mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-23 22:15:49 +00:00
feat(pockethost): webhooks
This commit is contained in:
parent
d601363e6f
commit
0cfed9530f
@ -31,6 +31,7 @@
|
|||||||
"@types/d3-scale-chromatic": "^3.1.0",
|
"@types/d3-scale-chromatic": "^3.1.0",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"cron-parser": "^5.3.0",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"d3-scale-chromatic": "^3.1.0",
|
"d3-scale-chromatic": "^3.1.0",
|
||||||
"daisyui": "^4.12.24",
|
"daisyui": "^4.12.24",
|
||||||
|
|||||||
@ -88,6 +88,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href={`/instances/${id}/secrets`} class={activeClass(`secrets`)}>Secrets</a>
|
<a href={`/instances/${id}/secrets`} class={activeClass(`secrets`)}>Secrets</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={`/instances/${id}/webhooks`} class={activeClass(`webhooks`)}>Webhooks</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={`/instances/${id}/logs`} class={activeClass(`logs`)}>Logs</a>
|
<a href={`/instances/${id}/logs`} class={activeClass(`logs`)}>Logs</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { assertExists } from 'pockethost/common'
|
||||||
|
import { instance } from '../store'
|
||||||
|
import SecretsInner from './Inner.svelte'
|
||||||
|
import CardHeader from '$src/components/cards/CardHeader.svelte'
|
||||||
|
|
||||||
|
$: ({ status, version, id } = $instance)
|
||||||
|
|
||||||
|
assertExists($instance, `Expected instance here`)
|
||||||
|
const { subdomain } = $instance
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{subdomain} webhooks - PocketHost</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="max-w-2xl">
|
||||||
|
<CardHeader documentation={`/docs/webhooks/`}>Webhooks</CardHeader>
|
||||||
|
<SecretsInner />
|
||||||
|
</div>
|
||||||
@ -0,0 +1,278 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import AlertBar from '$components/AlertBar.svelte'
|
||||||
|
import { client } from '$src/pocketbase-client/index.js'
|
||||||
|
import { reduce } from '@s-libs/micro-dash'
|
||||||
|
import { type UpdateInstancePayload } from 'pockethost/common'
|
||||||
|
import { instance } from '../store.js'
|
||||||
|
import { items } from './stores.js'
|
||||||
|
import { faFloppyDisk } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import Fa from 'svelte-fa'
|
||||||
|
|
||||||
|
// Validate webhook endpoint path (must start with /, can include query params, no host/protocol)
|
||||||
|
const validateWebhookEndpoint = (endpoint: string): boolean => {
|
||||||
|
if (!endpoint || endpoint.length === 0) return false
|
||||||
|
|
||||||
|
// Must start with /
|
||||||
|
if (!endpoint.startsWith('/')) return false
|
||||||
|
|
||||||
|
// Check if it contains protocol or host (should not)
|
||||||
|
if (endpoint.includes('://') || endpoint.includes('://')) return false
|
||||||
|
if (endpoint.includes('http://') || endpoint.includes('https://')) return false
|
||||||
|
|
||||||
|
// Basic path validation - should be a valid URL path
|
||||||
|
try {
|
||||||
|
// Create a fake URL to validate the path part
|
||||||
|
const testUrl = new URL(`http://example.com${endpoint}`)
|
||||||
|
const path = testUrl.pathname
|
||||||
|
|
||||||
|
// Path should be at least 2 characters (including the leading /)
|
||||||
|
if (path.length < 2) return false
|
||||||
|
|
||||||
|
// Should not contain invalid characters for URL paths
|
||||||
|
if (path.includes('//') || path.includes('\\')) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic cron expression validation
|
||||||
|
const validateCronExpression = (cronExpression: string): boolean => {
|
||||||
|
if (!cronExpression || cronExpression.length === 0) return false
|
||||||
|
|
||||||
|
const expression = cronExpression.trim()
|
||||||
|
|
||||||
|
// Check for predefined macros first
|
||||||
|
const validMacros = [
|
||||||
|
'@yearly',
|
||||||
|
'@annually',
|
||||||
|
'@monthly',
|
||||||
|
'@weekly',
|
||||||
|
'@daily',
|
||||||
|
'@midnight',
|
||||||
|
'@hourly',
|
||||||
|
'@minutely',
|
||||||
|
'@secondly',
|
||||||
|
'@weekdays',
|
||||||
|
'@weekends',
|
||||||
|
]
|
||||||
|
|
||||||
|
if (validMacros.includes(expression)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation: should have 5 parts separated by spaces
|
||||||
|
const parts = expression.split(/\s+/)
|
||||||
|
if (parts.length !== 5) return false
|
||||||
|
|
||||||
|
// Each part should contain valid cron characters
|
||||||
|
const validChars = /^[\d*,\-/?LW#]+$/
|
||||||
|
return parts.every((part) => validChars.test(part))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the new key and value to be added
|
||||||
|
let apiEndpoint: string = ''
|
||||||
|
let cronValue: string = ''
|
||||||
|
|
||||||
|
// These will validate the key and value before being submitted
|
||||||
|
let isApiEndpointValid = false
|
||||||
|
let isCronValueValid = false
|
||||||
|
let isFormValid = false
|
||||||
|
|
||||||
|
// This will animate a success message when the key is saved
|
||||||
|
let successfulSave = false
|
||||||
|
|
||||||
|
// Keep track of any error message
|
||||||
|
let errorMessage: string = ''
|
||||||
|
|
||||||
|
// Watch for changes in real time and update the key and value as the user types them
|
||||||
|
$: {
|
||||||
|
apiEndpoint = apiEndpoint.trim()
|
||||||
|
isApiEndpointValid = validateWebhookEndpoint(apiEndpoint)
|
||||||
|
isCronValueValid = cronValue.length > 0 && validateCronExpression(cronValue)
|
||||||
|
isFormValid = isApiEndpointValid && isCronValueValid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit the form to create the new environment variable
|
||||||
|
const handleSubmit = async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// Reset any messaging
|
||||||
|
errorMessage = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Block the button from submitting more than once
|
||||||
|
isFormValid = false
|
||||||
|
|
||||||
|
// Save to the database
|
||||||
|
items.upsert({ endpoint: apiEndpoint, value: cronValue.trim() })
|
||||||
|
await client().updateInstance({
|
||||||
|
id: $instance.id,
|
||||||
|
fields: {
|
||||||
|
webhooks: $items,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reset the values when the POST is done
|
||||||
|
apiEndpoint = ''
|
||||||
|
cronValue = ''
|
||||||
|
|
||||||
|
// Enable the submit button
|
||||||
|
isFormValid = true
|
||||||
|
|
||||||
|
// Show the success message
|
||||||
|
successfulSave = true
|
||||||
|
|
||||||
|
// Remove the success toast after a few seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
successfulSave = false
|
||||||
|
}, 5000)
|
||||||
|
} catch (error: any) {
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h3 class="text-xl">Add New Webhook</h3>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
{#if successfulSave}
|
||||||
|
<AlertBar message="Your new webhook has been saved." type="success" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<AlertBar message={errorMessage} type="error" />
|
||||||
|
|
||||||
|
<form on:submit={handleSubmit} class="mb-4">
|
||||||
|
<div class="flex flex-row gap-4 mb-4">
|
||||||
|
<label class="flex-1 form-control">
|
||||||
|
<input
|
||||||
|
id="webhook-api-endpoint"
|
||||||
|
type="text"
|
||||||
|
bind:value={apiEndpoint}
|
||||||
|
placeholder="/api/webhooks/my-endpoint"
|
||||||
|
class={`input input-bordered ${!isApiEndpointValid && apiEndpoint.length > 0 ? 'input-error text-error' : ''}`}
|
||||||
|
/>
|
||||||
|
{#if !isApiEndpointValid && apiEndpoint.length > 0}
|
||||||
|
<div class="label">
|
||||||
|
<span class="text-error">
|
||||||
|
API endpoints must be valid paths starting with / (e.g., <code>/api/webhooks/my-webhook</code> or
|
||||||
|
<code>/api/cron?token=abc</code>). Do not include protocol or host.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
<div class="flex-1 form-control">
|
||||||
|
<input
|
||||||
|
id="webhook-schedule"
|
||||||
|
type="text"
|
||||||
|
bind:value={cronValue}
|
||||||
|
placeholder="Schedule (UTC time)"
|
||||||
|
class={`input input-bordered ${!isCronValueValid && cronValue.length > 0 ? 'input-error text-error' : ''}`}
|
||||||
|
/>
|
||||||
|
{#if !isCronValueValid && cronValue.length > 0}
|
||||||
|
<div class="label">
|
||||||
|
<span class="text-error">
|
||||||
|
Please enter a valid cron expression (e.g., <code>0 9 * * 1-5</code> for weekdays at 9 AM UTC, or
|
||||||
|
<code>@daily</code> for daily at midnight UTC).
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-none text-right">
|
||||||
|
<button type="submit" class="btn btn-primary" disabled={!isFormValid}>
|
||||||
|
Add <Fa icon={faFloppyDisk} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p>
|
||||||
|
The schedule is a cron expression that defines when the webhook will be called in <strong>UTC time</strong>. For
|
||||||
|
example,
|
||||||
|
<code>0 0 * * *</code> means every day at midnight UTC.
|
||||||
|
</p>
|
||||||
|
<table class="table table-sm w-full mt-4">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Allowed Values</th>
|
||||||
|
<th>Special Characters</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>Minute</code></td>
|
||||||
|
<td>0-59</td>
|
||||||
|
<td><code>,</code> <code>-</code> <code>*</code> <code>/</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>Hour</code></td>
|
||||||
|
<td>0-23</td>
|
||||||
|
<td><code>,</code> <code>-</code> <code>*</code> <code>/</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>Day of Month</code></td>
|
||||||
|
<td>1-31</td>
|
||||||
|
<td
|
||||||
|
><code>,</code> <code>-</code> <code>*</code> <code>/</code> <code>?</code> <code>L</code>
|
||||||
|
<code>W</code></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>Month</code></td>
|
||||||
|
<td>1-12 or JAN-DEC</td>
|
||||||
|
<td><code>,</code> <code>-</code> <code>*</code> <code>/</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>Day of Week</code></td>
|
||||||
|
<td>0-6 or SUN-SAT</td>
|
||||||
|
<td
|
||||||
|
><code>,</code> <code>-</code> <code>*</code> <code>/</code> <code>?</code> <code>L</code>
|
||||||
|
<code>#</code></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="mt-2">
|
||||||
|
<strong>Common Macros:</strong>
|
||||||
|
<ul class="list-disc pl-6">
|
||||||
|
<li><code>@yearly</code>: <code>0 0 1 1 *</code> (once a year at midnight, Jan 1)</li>
|
||||||
|
<li><code>@annually</code>: <code>0 0 1 1 *</code> (same as <code>@yearly</code>)</li>
|
||||||
|
<li><code>@monthly</code>: <code>0 0 1 * *</code> (once a month at midnight, first day)</li>
|
||||||
|
<li><code>@weekly</code>: <code>0 0 * * 0</code> (once a week at midnight, Sunday)</li>
|
||||||
|
<li><code>@daily</code>: <code>0 0 * * *</code> (once a day at midnight)</li>
|
||||||
|
<li><code>@midnight</code>: <code>0 0 * * *</code> (same as <code>@daily</code>)</li>
|
||||||
|
<li><code>@hourly</code>: <code>0 * * * *</code> (once an hour at minute 0)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<strong>Practical Examples:</strong>
|
||||||
|
<ul class="list-disc pl-6">
|
||||||
|
<li><code>0 9 * * 1-5</code> - Every weekday at 9:00 AM</li>
|
||||||
|
<li><code>0 12 * * 1</code> - Every Monday at noon</li>
|
||||||
|
<li><code>0 0 1 * *</code> - First day of every month at midnight</li>
|
||||||
|
<li><code>0 18 * * 5</code> - Every Friday at 6:00 PM</li>
|
||||||
|
<li><code>30 2 * * *</code> - Every day at 2:30 AM</li>
|
||||||
|
<li><code>0 */6 * * *</code> - Every 6 hours (00:00, 06:00, 12:00, 18:00)</li>
|
||||||
|
<li><code>0 0 * * 0</code> - Every Sunday at midnight</li>
|
||||||
|
<li><code>0 8 15 * *</code> - 15th of every month at 8:00 AM</li>
|
||||||
|
<li><code>0 0 1 1 *</code> - New Year's Day at midnight</li>
|
||||||
|
<li><code>0 12 * * 0,6</code> - Weekends at noon</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(code) {
|
||||||
|
background-color: #565656;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CodeSample from '$components/CodeSample.svelte'
|
||||||
|
import { forEach } from '@s-libs/micro-dash'
|
||||||
|
import { instance } from '../store'
|
||||||
|
import Form from './Form.svelte'
|
||||||
|
import List from './List.svelte'
|
||||||
|
import { items } from './stores'
|
||||||
|
import Fa from 'svelte-fa'
|
||||||
|
import { faUserSecret } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const { id, webhooks } = $instance
|
||||||
|
items.clear()
|
||||||
|
|
||||||
|
forEach(webhooks, (hook) => {
|
||||||
|
items.upsert(hook)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: code =
|
||||||
|
`// pb_hooks/my-webhook.pb.js\n\n` +
|
||||||
|
($items.length > 0
|
||||||
|
? $items
|
||||||
|
.map(
|
||||||
|
({ endpoint, value }) => `routerAdd("GET", "${endpoint}", (e) => {
|
||||||
|
return e.json(200, { "message": "Webhook called" })
|
||||||
|
}))`
|
||||||
|
)
|
||||||
|
.join('\n')
|
||||||
|
: ``)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
Webhooks let you call API endpoints on your instance at scheduled times, replacing PocketBase's standard cron
|
||||||
|
scheduler.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- If the user has any secrets, render them in a code block -->
|
||||||
|
{#if $items.length > 0}
|
||||||
|
<div class="mb-8">
|
||||||
|
<CodeSample {code} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $items.length === 0}
|
||||||
|
<div class="alert border-2 border-primary mb-8">
|
||||||
|
<Fa icon={faUserSecret} />
|
||||||
|
<span>No webhooks yet. Create your first webhook to get started.</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<List />
|
||||||
|
{/if}
|
||||||
|
<Form />
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition'
|
||||||
|
import { items } from './stores'
|
||||||
|
import { client } from '$src/pocketbase-client'
|
||||||
|
import { instance } from '../store'
|
||||||
|
import { reduce } from '@s-libs/micro-dash'
|
||||||
|
import { logger, type UpdateInstancePayload } from 'pockethost/common'
|
||||||
|
import Fa from 'svelte-fa'
|
||||||
|
import { faTrash, faChevronDown, faChevronRight } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
// Track which webhooks have expanded details
|
||||||
|
let expandedWebhooks: Set<string> = new Set()
|
||||||
|
|
||||||
|
const toggleExpanded = (endpoint: string) => {
|
||||||
|
if (expandedWebhooks.has(endpoint)) {
|
||||||
|
expandedWebhooks.delete(endpoint)
|
||||||
|
} else {
|
||||||
|
expandedWebhooks.add(endpoint)
|
||||||
|
}
|
||||||
|
expandedWebhooks = expandedWebhooks
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status: number | undefined) => {
|
||||||
|
if (!status) return 'text-gray-400'
|
||||||
|
if (status >= 200 && status < 300) return 'text-success'
|
||||||
|
if (status >= 400) return 'text-error'
|
||||||
|
return 'text-warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTimestamp = (timestamp: number) => {
|
||||||
|
return new Date(timestamp).toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (name: string) => async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
logger().debug(`Deleting ${name}`)
|
||||||
|
items.delete(name)
|
||||||
|
await client().updateInstance({
|
||||||
|
id: $instance.id,
|
||||||
|
fields: {
|
||||||
|
webhooks: $items,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class="table mb-8">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-1/3 border-b-2 border-neutral">API Endpoint</th>
|
||||||
|
<th class="w-1/3 border-b-2 border-neutral">Schedule</th>
|
||||||
|
<th class="w-1/6 border-b-2 border-neutral">Status</th>
|
||||||
|
<th class="w-1/6 border-b-2 border-neutral text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{#each $items as item}
|
||||||
|
<tr transition:fade>
|
||||||
|
<th>{item.endpoint}</th>
|
||||||
|
<td>{item.value}</td>
|
||||||
|
<td>
|
||||||
|
{#if item.lastFired}
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost btn-sm p-1 {getStatusColor(item.lastFired.response.status)}"
|
||||||
|
on:click={() => toggleExpanded(item.endpoint)}
|
||||||
|
title="Click to view response details"
|
||||||
|
>
|
||||||
|
<Fa icon={expandedWebhooks.has(item.endpoint) ? faChevronDown : faChevronRight} class="mr-1" />
|
||||||
|
<span class="font-mono font-bold">{item.lastFired.response.status}</span>
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<span class="text-gray-400">No runs yet</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<button
|
||||||
|
aria-label="Delete"
|
||||||
|
on:click={handleDelete(item.endpoint)}
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-square btn-outline btn-warning"><Fa icon={faTrash} /></button
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{#if item.lastFired && expandedWebhooks.has(item.endpoint)}
|
||||||
|
<tr transition:fade>
|
||||||
|
<td colspan="4" class="bg-base-200 p-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h4 class="font-semibold">Last Execution Details</h4>
|
||||||
|
<span class="text-sm text-gray-500">{formatTimestamp(item.lastFired.timestamp)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="font-medium text-sm">Status Code:</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<span
|
||||||
|
class="badge {item.lastFired.response.status >= 200 && item.lastFired.response.status < 300
|
||||||
|
? 'badge-success'
|
||||||
|
: 'badge-error'}"
|
||||||
|
>
|
||||||
|
{item.lastFired.response.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="md:col-span-1">
|
||||||
|
<label class="font-medium text-sm">Response Body:</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<pre class="text-xs bg-base-300 p-2 rounded max-h-32 overflow-y-auto">{item.lastFired.response
|
||||||
|
.body || '(empty)'}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { scaleOrdinal } from 'd3-scale'
|
||||||
|
import { schemeTableau10 } from 'd3-scale-chromatic'
|
||||||
|
import { type InstanceWebhookCollection, type InstanceWebhookItem } from 'pockethost/common'
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
// color scale used in both visualizations
|
||||||
|
const colorScale = scaleOrdinal(schemeTableau10)
|
||||||
|
|
||||||
|
// Use the proper types from the schema
|
||||||
|
export type CronItem = InstanceWebhookItem
|
||||||
|
export type CronArray = InstanceWebhookCollection
|
||||||
|
|
||||||
|
// function to sort the input array and add a color according to the sorted values
|
||||||
|
function formatInput(input: CronArray): CronArray {
|
||||||
|
return input
|
||||||
|
.sort((a, b) => (a.endpoint < b.endpoint ? -1 : 1))
|
||||||
|
.map(({ endpoint, value, lastFired }) => ({
|
||||||
|
endpoint,
|
||||||
|
value,
|
||||||
|
lastFired,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitize = (item: CronItem) => {
|
||||||
|
return {
|
||||||
|
endpoint: item.endpoint.trim(),
|
||||||
|
value: item.value.trim(),
|
||||||
|
lastFired: item.lastFired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a custom store fulfilling the CRUD operations
|
||||||
|
function createItems(initialItems: CronArray) {
|
||||||
|
const { subscribe, set, update } = writable(initialItems)
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
subscribe,
|
||||||
|
clear: () => {
|
||||||
|
set([])
|
||||||
|
},
|
||||||
|
// create: add an object for the item at the end of the store's array
|
||||||
|
upsert: (item: CronItem) => {
|
||||||
|
const { endpoint, value, lastFired } = sanitize(item)
|
||||||
|
|
||||||
|
return update((n) => {
|
||||||
|
return formatInput([...n.filter((i) => i.endpoint !== endpoint), { endpoint, value, lastFired }])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// delete: remove the item from the array
|
||||||
|
delete: (name: string) => {
|
||||||
|
return update((n) => {
|
||||||
|
const index = n.findIndex((item) => item.endpoint === name)
|
||||||
|
n = [...n.slice(0, index), ...n.slice(index + 1)]
|
||||||
|
return formatInput(n)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
export const items = createItems(formatInput([]))
|
||||||
@ -32,6 +32,7 @@
|
|||||||
<DocLink path="logs" title="Logging" />
|
<DocLink path="logs" title="Logging" />
|
||||||
<DocLink path="dev-mode" title="Dev Mode" />
|
<DocLink path="dev-mode" title="Dev Mode" />
|
||||||
<DocLink path="secrets" title="Secrets" />
|
<DocLink path="secrets" title="Secrets" />
|
||||||
|
<DocLink path="webhooks" title="Webhooks" />
|
||||||
<DocLink path="ftp" title="FTP Access" />
|
<DocLink path="ftp" title="FTP Access" />
|
||||||
<DocLink path="admin-sync" title="Admin Sync" />
|
<DocLink path="admin-sync" title="Admin Sync" />
|
||||||
<DocLink path="js" title="Extending via JS" />
|
<DocLink path="js" title="Extending via JS" />
|
||||||
|
|||||||
262
packages/dashboard/src/routes/(static)/docs/webhooks/+page.md
Normal file
262
packages/dashboard/src/routes/(static)/docs/webhooks/+page.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# Webhooks
|
||||||
|
|
||||||
|
Webhooks allow you to schedule API calls to your PocketBase instance at specific times, replacing the need for external cron job schedulers. This feature enables automated tasks like data cleanup, backups, notifications, and integrations with external services.
|
||||||
|
|
||||||
|
> **Important**: Webhooks replace PocketBase's built-in [cron job scheduling](https://pocketbase.io/docs/js-jobs-scheduling/) (`cronAdd`) on PocketHost. While `cronAdd` works in standard PocketBase deployments, it becomes unreliable on PocketHost due to instance hibernation. Scheduled webhooks will always execute reliably, even when your instance is hibernated.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Webhooks are configured through the PocketHost dashboard and automatically execute HTTP requests to your specified endpoints at scheduled intervals. Each webhook consists of:
|
||||||
|
|
||||||
|
- **API Endpoint**: The URL path within your instance to call
|
||||||
|
- **Schedule**: A cron expression defining when the webhook executes
|
||||||
|
|
||||||
|
All webhooks execute in **UTC time**. Make sure to adjust your cron schedules accordingly.
|
||||||
|
|
||||||
|
### Why Use Webhooks Instead of `cronAdd`?
|
||||||
|
|
||||||
|
On PocketHost, webhooks provide several advantages over PocketBase's built-in `cronAdd`:
|
||||||
|
|
||||||
|
- **Reliability**: Webhooks execute even when your instance is hibernated
|
||||||
|
- **Consistency**: No dependency on your instance's uptime
|
||||||
|
- **Scalability**: Handled by PocketHost's infrastructure, not your instance
|
||||||
|
- **Monitoring**: Better visibility into execution status and failures
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### API Endpoint
|
||||||
|
|
||||||
|
The API endpoint must be a valid path within your PocketBase instance:
|
||||||
|
|
||||||
|
- Must start with `/` (e.g., `/api/webhooks/backup`)
|
||||||
|
- Can include query parameters (e.g., `/api/cron?token=abc123`)
|
||||||
|
- Cannot include protocol or host (no `http://` or `https://`)
|
||||||
|
- Supports any valid URL path structure
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/api/webhooks/daily-cleanup`
|
||||||
|
- `/api/backup?type=full&compress=true`
|
||||||
|
- `/webhook/slack/notifications`
|
||||||
|
- `/api/maintenance/cleanup-old-records`
|
||||||
|
|
||||||
|
### Schedule (Cron Expression)
|
||||||
|
|
||||||
|
Webhooks use standard cron expressions to define execution schedules. You can use either:
|
||||||
|
|
||||||
|
#### Predefined Macros
|
||||||
|
|
||||||
|
| Macro | Description | Equivalent Expression |
|
||||||
|
| ----------- | ------------------------------------ | --------------------- |
|
||||||
|
| `@yearly` | Once a year at midnight, January 1st | `0 0 1 1 *` |
|
||||||
|
| `@annually` | Same as `@yearly` | `0 0 1 1 *` |
|
||||||
|
| `@monthly` | Once a month at midnight, first day | `0 0 1 * *` |
|
||||||
|
| `@weekly` | Once a week at midnight on Sunday | `0 0 * * 0` |
|
||||||
|
| `@daily` | Once a day at midnight | `0 0 * * *` |
|
||||||
|
| `@midnight` | Same as `@daily` | `0 0 * * *` |
|
||||||
|
| `@hourly` | Once an hour at the beginning | `0 * * * *` |
|
||||||
|
| `@minutely` | Once a minute | `* * * * *` |
|
||||||
|
| `@secondly` | Once a second | `* * * * * *` |
|
||||||
|
| `@weekdays` | Every weekday at midnight | `0 0 * * 1-5` |
|
||||||
|
| `@weekends` | Every weekend at midnight | `0 0 * * 0,6` |
|
||||||
|
|
||||||
|
#### Standard Cron Expressions
|
||||||
|
|
||||||
|
Standard cron expressions use 5 fields: `minute hour day month weekday`
|
||||||
|
|
||||||
|
| Field | Values | Special Characters | Description |
|
||||||
|
| ------------ | ------ | ------------------ | -------------------------- |
|
||||||
|
| Minute | 0-59 | `* , - / ?` | Minute of the hour |
|
||||||
|
| Hour | 0-23 | `* , - / ?` | Hour of the day |
|
||||||
|
| Day of Month | 1-31 | `* , - / ? L W` | Day of the month |
|
||||||
|
| Month | 1-12 | `* , - / ?` | Month of the year |
|
||||||
|
| Day of Week | 0-6 | `* , - / ? L #` | Day of the week (0=Sunday) |
|
||||||
|
|
||||||
|
**Special Characters:**
|
||||||
|
|
||||||
|
- `*` - Any value
|
||||||
|
- `,` - Value list separator
|
||||||
|
- `-` - Range of values
|
||||||
|
- `/` - Step values
|
||||||
|
- `?` - Any value (alias for `*`)
|
||||||
|
- `L` - Last day of month/week
|
||||||
|
- `W` - Weekday (nearest to given day)
|
||||||
|
- `#` - Nth day of month
|
||||||
|
|
||||||
|
### Timing
|
||||||
|
|
||||||
|
All webhooks execute in **UTC time**. When scheduling webhooks, convert your local time to UTC:
|
||||||
|
|
||||||
|
- **EST (UTC-5)**: 9 AM EST = 2 PM UTC (14:00)
|
||||||
|
- **PST (UTC-8)**: 6 PM PST = 2 AM UTC next day (02:00)
|
||||||
|
- **GMT+3**: 3 PM = 12 PM UTC (12:00)
|
||||||
|
|
||||||
|
Use online UTC converters to help calculate the correct schedule times.
|
||||||
|
|
||||||
|
## Common Examples
|
||||||
|
|
||||||
|
### Business Operations (UTC Time)
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Weekdays at 9 AM UTC
|
||||||
|
0 9 * * 1-5
|
||||||
|
|
||||||
|
# Every Monday at noon UTC
|
||||||
|
0 12 * * 1
|
||||||
|
|
||||||
|
# Every Friday at 6 PM UTC
|
||||||
|
0 18 * * 5
|
||||||
|
|
||||||
|
# First day of every month at midnight UTC
|
||||||
|
0 0 1 * *
|
||||||
|
|
||||||
|
# 15th of every month at 8 AM UTC
|
||||||
|
0 8 15 * *
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Management (UTC Time)
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Daily backup at 2 AM UTC
|
||||||
|
0 2 * * *
|
||||||
|
|
||||||
|
# Cleanup old records every 6 hours
|
||||||
|
0 */6 * * *
|
||||||
|
|
||||||
|
# Weekly data export on Sundays at midnight UTC
|
||||||
|
0 0 * * 0
|
||||||
|
|
||||||
|
# Monthly maintenance on the 1st at midnight UTC
|
||||||
|
0 0 1 * *
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Macros
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Daily operations
|
||||||
|
@daily
|
||||||
|
|
||||||
|
# Weekly reports
|
||||||
|
@weekly
|
||||||
|
|
||||||
|
# Monthly cleanup
|
||||||
|
@monthly
|
||||||
|
|
||||||
|
# Business hours only
|
||||||
|
@weekdays
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Creating Webhook Endpoints
|
||||||
|
|
||||||
|
Create API endpoints in your PocketBase instance to handle webhook requests using [PocketBase's routing system](https://pocketbase.io/docs/js-routing/):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// pb_hooks/onRequest.pb.js
|
||||||
|
routerAdd('POST', '/api/webhooks/backup', (e) => {
|
||||||
|
// Your backup logic here
|
||||||
|
console.log('Backup webhook triggered')
|
||||||
|
|
||||||
|
// Example: Create a backup record
|
||||||
|
const backup = new Record($app.dao().findCollectionByNameOrId('backups'), {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
status: 'completed',
|
||||||
|
size: '1.2GB',
|
||||||
|
})
|
||||||
|
|
||||||
|
$app.dao().saveRecord(backup)
|
||||||
|
|
||||||
|
return e.json(200, { status: 'success' })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
Webhooks should return appropriate HTTP status codes:
|
||||||
|
|
||||||
|
- `200` - Success
|
||||||
|
- `400` - Bad request
|
||||||
|
- `500` - Internal server error
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
routerAdd('POST', '/api/webhooks/cleanup', (e) => {
|
||||||
|
try {
|
||||||
|
// Your cleanup logic
|
||||||
|
return e.json(200, { status: 'success' })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Webhook error:', error)
|
||||||
|
return e.json(500, { error: 'Internal server error' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
For secure webhooks, include authentication in your endpoints:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
routerAdd('POST', '/api/webhooks/secure', (e) => {
|
||||||
|
const token = e.request.url.query().get('token')
|
||||||
|
|
||||||
|
if (token !== process.env.WEBHOOK_SECRET) {
|
||||||
|
return e.json(401, { error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your secure webhook logic
|
||||||
|
return e.json(200, { status: 'success' })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Idempotency
|
||||||
|
|
||||||
|
Make your webhooks idempotent so they can be safely retried:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
routerAdd('POST', '/api/webhooks/process', (e) => {
|
||||||
|
const jobId = e.request.url.query().get('jobId')
|
||||||
|
|
||||||
|
// Check if already processed
|
||||||
|
const existing = $app.dao().findFirstRecordByData('jobs', 'jobId', jobId)
|
||||||
|
if (existing && existing.get('status') === 'completed') {
|
||||||
|
return e.json(200, { status: 'already_processed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the job
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Logging
|
||||||
|
|
||||||
|
Always log webhook executions for debugging:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
routerAdd('POST', '/api/webhooks/backup', (e) => {
|
||||||
|
console.log(`Backup webhook triggered at ${new Date().toISOString()}`)
|
||||||
|
|
||||||
|
// Your backup logic
|
||||||
|
|
||||||
|
console.log('Backup webhook completed successfully')
|
||||||
|
return e.json(200, { status: 'success' })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Webhook not executing**: Check the cron expression syntax and ensure times are in UTC
|
||||||
|
2. **Endpoint not found**: Ensure the API endpoint exists in your PocketBase instance using [PocketBase routing](https://pocketbase.io/docs/js-routing/)
|
||||||
|
3. **Authentication errors**: Verify any required tokens or secrets
|
||||||
|
4. **Timeout errors**: Optimize webhook execution time
|
||||||
|
5. **Using `cronAdd` instead of webhooks**: Replace `cronAdd` calls with scheduled webhooks for reliable execution on PocketHost
|
||||||
|
6. **Wrong execution time**: Remember all schedules are in UTC - convert your local time accordingly
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Concurrent webhook executions may be limited
|
||||||
|
- Webhooks may not run exactly at the time specified, depending on system load and instance state
|
||||||
|
- Webhooks are triggered by PocketHost's scheduling system, not your instance's internal clock
|
||||||
@ -25,6 +25,13 @@ export type InstanceWebhookCollection = InstanceWebhookItem[]
|
|||||||
export type InstanceWebhookItem = {
|
export type InstanceWebhookItem = {
|
||||||
endpoint: InstanceWebhookEndpoint
|
endpoint: InstanceWebhookEndpoint
|
||||||
value: InstanceWebhookValue
|
value: InstanceWebhookValue
|
||||||
|
lastFired?: {
|
||||||
|
timestamp: number
|
||||||
|
response: {
|
||||||
|
status: number
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstanceFields<TExtra = {}> = BaseFields & {
|
export type InstanceFields<TExtra = {}> = BaseFields & {
|
||||||
|
|||||||
@ -251,6 +251,7 @@ const HandleInstanceUpdate = (c) => {
|
|||||||
power: null,
|
power: null,
|
||||||
version: null,
|
version: null,
|
||||||
secrets: null,
|
secrets: null,
|
||||||
|
webhooks: null,
|
||||||
syncAdmin: null,
|
syncAdmin: null,
|
||||||
dev: null,
|
dev: null,
|
||||||
cname: null
|
cname: null
|
||||||
@ -260,13 +261,14 @@ const HandleInstanceUpdate = (c) => {
|
|||||||
log(`After bind`);
|
log(`After bind`);
|
||||||
data = JSON.parse(JSON.stringify(data));
|
data = JSON.parse(JSON.stringify(data));
|
||||||
const id = c.pathParam("id");
|
const id = c.pathParam("id");
|
||||||
const { fields: { subdomain, power, version, secrets, syncAdmin, dev, cname } } = data;
|
const { fields: { subdomain, power, version, secrets, webhooks, syncAdmin, dev, cname } } = data;
|
||||||
log(`vars`, JSON.stringify({
|
log(`vars`, JSON.stringify({
|
||||||
id,
|
id,
|
||||||
subdomain,
|
subdomain,
|
||||||
power,
|
power,
|
||||||
version,
|
version,
|
||||||
secrets,
|
secrets,
|
||||||
|
webhooks,
|
||||||
syncAdmin,
|
syncAdmin,
|
||||||
dev,
|
dev,
|
||||||
cname
|
cname
|
||||||
@ -289,6 +291,7 @@ const HandleInstanceUpdate = (c) => {
|
|||||||
version,
|
version,
|
||||||
power,
|
power,
|
||||||
secrets,
|
secrets,
|
||||||
|
webhooks,
|
||||||
syncAdmin,
|
syncAdmin,
|
||||||
dev,
|
dev,
|
||||||
cname
|
cname
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("etae8tuiaxl6xfv")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "5q2bapw9",
|
||||||
|
"name": "webhooks",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("etae8tuiaxl6xfv")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("5q2bapw9")
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { type InstanceWebhookCollection } from '$common'
|
||||||
import { mkLog, StringKvLookup } from '$util/Logger'
|
import { mkLog, StringKvLookup } from '$util/Logger'
|
||||||
import { removeEmptyKeys } from '$util/removeEmptyKeys'
|
import { removeEmptyKeys } from '$util/removeEmptyKeys'
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
|||||||
power: null,
|
power: null,
|
||||||
version: null,
|
version: null,
|
||||||
secrets: null,
|
secrets: null,
|
||||||
|
webhooks: null,
|
||||||
syncAdmin: null,
|
syncAdmin: null,
|
||||||
dev: null,
|
dev: null,
|
||||||
cname: null,
|
cname: null,
|
||||||
@ -81,6 +83,7 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
|||||||
power: boolean | null
|
power: boolean | null
|
||||||
version: string | null
|
version: string | null
|
||||||
secrets: StringKvLookup | null
|
secrets: StringKvLookup | null
|
||||||
|
webhooks: InstanceWebhookCollection | null
|
||||||
syncAdmin: boolean | null
|
syncAdmin: boolean | null
|
||||||
dev: boolean | null
|
dev: boolean | null
|
||||||
cname: string | null
|
cname: string | null
|
||||||
@ -95,7 +98,7 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
|||||||
|
|
||||||
const id = c.pathParam('id')
|
const id = c.pathParam('id')
|
||||||
const {
|
const {
|
||||||
fields: { subdomain, power, version, secrets, syncAdmin, dev, cname },
|
fields: { subdomain, power, version, secrets, webhooks, syncAdmin, dev, cname },
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
log(
|
log(
|
||||||
@ -106,6 +109,7 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
|||||||
power,
|
power,
|
||||||
version,
|
version,
|
||||||
secrets,
|
secrets,
|
||||||
|
webhooks,
|
||||||
syncAdmin,
|
syncAdmin,
|
||||||
dev,
|
dev,
|
||||||
cname,
|
cname,
|
||||||
@ -143,6 +147,7 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
|||||||
version,
|
version,
|
||||||
power,
|
power,
|
||||||
secrets,
|
secrets,
|
||||||
|
webhooks,
|
||||||
syncAdmin,
|
syncAdmin,
|
||||||
dev,
|
dev,
|
||||||
cname,
|
cname,
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"$common": ["../common/index.ts"],
|
||||||
"$util/*": ["src/lib/util/*"]
|
"$util/*": ["src/lib/util/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
118
packages/pockethost/src/services/CronService/index.ts
Normal file
118
packages/pockethost/src/services/CronService/index.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
InstanceFields,
|
||||||
|
InstanceId,
|
||||||
|
LoggerService,
|
||||||
|
mkInstanceUrl,
|
||||||
|
mkSingleton,
|
||||||
|
MothershipAdminClientService,
|
||||||
|
SingletonBaseConfig,
|
||||||
|
} from '@'
|
||||||
|
import Bottleneck from 'bottleneck'
|
||||||
|
import { CronJob } from 'cron'
|
||||||
|
import { MothershipMirrorService } from '../MothershipMirrorService'
|
||||||
|
|
||||||
|
export type CronServiceConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
|
export const CronService = mkSingleton(async (config: Partial<CronServiceConfig>) => {
|
||||||
|
const mirror = await MothershipMirrorService()
|
||||||
|
const logger = (config.logger ?? LoggerService()).create(`CronService`)
|
||||||
|
const { dbg, error, info, warn } = logger
|
||||||
|
info(`Starting`)
|
||||||
|
|
||||||
|
const { client } = await MothershipAdminClientService()
|
||||||
|
|
||||||
|
const limiter = new Bottleneck({ maxConcurrent: 10 })
|
||||||
|
const jobs: Map<InstanceId, Set<CronJob>> = new Map()
|
||||||
|
|
||||||
|
const removeJobsForInstanceId = (instanceId: InstanceId) => {
|
||||||
|
if (jobs.has(instanceId)) {
|
||||||
|
dbg(`Stopping jobs for instance ${instanceId}`)
|
||||||
|
jobs.get(instanceId)?.forEach((job) => {
|
||||||
|
job.stop()
|
||||||
|
})
|
||||||
|
dbg(`Deleted jobs for instance ${instanceId}`)
|
||||||
|
jobs.delete(instanceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertInstance = async (instance: InstanceFields) => {
|
||||||
|
removeJobsForInstanceId(instance.id)
|
||||||
|
const newJobs = new Set<CronJob>()
|
||||||
|
const { webhooks } = instance
|
||||||
|
if (!webhooks) {
|
||||||
|
dbg(`Instance ${instance.id} has no webhooks`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dbg(`Instance has ${instance.webhooks?.length} webhooks`)
|
||||||
|
dbg(`Instance has ${jobs.get(instance.id)?.size} jobs`)
|
||||||
|
dbg(`Creating new jobs for instance ${instance.id}`)
|
||||||
|
webhooks.forEach((webhook) => {
|
||||||
|
if (!webhook.value) return
|
||||||
|
dbg(`Creating new job for webhook ${webhook.endpoint} (${webhook.value}) for instance ${instance.id}`)
|
||||||
|
const job = new CronJob(
|
||||||
|
webhook.value, // cronTime
|
||||||
|
() => {
|
||||||
|
dbg(`Firing webhook ${webhook.endpoint} (${webhook.value}) for instance ${instance.id}`)
|
||||||
|
limiter.schedule(async () => {
|
||||||
|
const url = mkInstanceUrl(instance, ...webhook.endpoint.split('/').filter(Boolean))
|
||||||
|
dbg(`Firing webhook ${url}`)
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {})
|
||||||
|
const body = await response.text()
|
||||||
|
|
||||||
|
// fetch only throws for network errors, not HTTP error status codes
|
||||||
|
// so 404, 500, etc. will be handled here without throwing
|
||||||
|
webhook.lastFired = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
response: {
|
||||||
|
status: response.status,
|
||||||
|
body: body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally log non-2xx responses
|
||||||
|
if (!response.ok) {
|
||||||
|
warn(`Webhook ${url} returned status ${response.status}: ${body}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// This catch block only handles network errors (connection failed, etc.)
|
||||||
|
webhook.lastFired = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
response: {
|
||||||
|
status: 500,
|
||||||
|
body: e instanceof Error ? e.message : String(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
error(`Network error firing webhook ${url}: ${e}`)
|
||||||
|
}
|
||||||
|
dbg(`Updating instance ${instance.id} with webhooks`, instance.webhooks)
|
||||||
|
client.updateInstance(instance.id, {
|
||||||
|
webhooks: instance.webhooks,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
job.start()
|
||||||
|
newJobs.add(job)
|
||||||
|
})
|
||||||
|
jobs.set(instance.id, newJobs)
|
||||||
|
dbg(`Created ${newJobs.size} jobs for instance ${instance.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror.onInstanceUpserted((instance) => {
|
||||||
|
dbg(`Instance upserted: ${instance.id}`)
|
||||||
|
upsertInstance(instance)
|
||||||
|
})
|
||||||
|
|
||||||
|
mirror.onInstanceDeleted((instanceId) => {
|
||||||
|
dbg(`Instance deleted: ${instanceId}`)
|
||||||
|
removeJobsForInstanceId(instanceId)
|
||||||
|
})
|
||||||
|
|
||||||
|
dbg(`Upserting instances`)
|
||||||
|
for (const instance of mirror.getInstances()) {
|
||||||
|
upsertInstance(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
})
|
||||||
189
pnpm-lock.yaml
generated
189
pnpm-lock.yaml
generated
@ -97,6 +97,9 @@ importers:
|
|||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.21
|
specifier: ^10.4.21
|
||||||
version: 10.4.21(postcss@8.5.6)
|
version: 10.4.21(postcss@8.5.6)
|
||||||
|
cron-parser:
|
||||||
|
specifier: ^5.3.0
|
||||||
|
version: 5.3.0
|
||||||
d3-scale:
|
d3-scale:
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
@ -175,6 +178,9 @@ importers:
|
|||||||
cors:
|
cors:
|
||||||
specifier: ^2.8.5
|
specifier: ^2.8.5
|
||||||
version: 2.8.5
|
version: 2.8.5
|
||||||
|
cron:
|
||||||
|
specifier: ^4.3.2
|
||||||
|
version: 4.3.2
|
||||||
devcert:
|
devcert:
|
||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
@ -256,12 +262,6 @@ importers:
|
|||||||
vhost:
|
vhost:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
winston:
|
|
||||||
specifier: ^3.17.0
|
|
||||||
version: 3.17.0
|
|
||||||
winston-transport:
|
|
||||||
specifier: ^4.9.0
|
|
||||||
version: 4.9.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/better-sqlite3':
|
'@types/better-sqlite3':
|
||||||
specifier: ^7.6.12
|
specifier: ^7.6.12
|
||||||
@ -477,17 +477,10 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@colors/colors@1.6.0':
|
|
||||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
|
||||||
engines: {node: '>=0.1.90'}
|
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@dabh/diagnostics@2.0.3':
|
|
||||||
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
|
||||||
|
|
||||||
'@emnapi/core@1.4.4':
|
'@emnapi/core@1.4.4':
|
||||||
resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==}
|
resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==}
|
||||||
|
|
||||||
@ -1743,6 +1736,9 @@ packages:
|
|||||||
'@types/lodash@4.17.14':
|
'@types/lodash@4.17.14':
|
||||||
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
|
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
|
||||||
|
|
||||||
|
'@types/luxon@3.6.2':
|
||||||
|
resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||||
|
|
||||||
@ -1803,9 +1799,6 @@ packages:
|
|||||||
'@types/tmp@0.0.33':
|
'@types/tmp@0.0.33':
|
||||||
resolution: {integrity: sha512-gVC1InwyVrO326wbBZw+AO3u2vRXz/iRWq9jYhpG4W8LXyIgDv3ZmcLQ5Q4Gs+gFMyqx+viFoFT+l3p61QFCmQ==}
|
resolution: {integrity: sha512-gVC1InwyVrO326wbBZw+AO3u2vRXz/iRWq9jYhpG4W8LXyIgDv3ZmcLQ5Q4Gs+gFMyqx+viFoFT+l3p61QFCmQ==}
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5':
|
|
||||||
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
|
||||||
|
|
||||||
'@types/unist@2.0.11':
|
'@types/unist@2.0.11':
|
||||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||||
|
|
||||||
@ -1916,9 +1909,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
async@3.2.6:
|
|
||||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
|
||||||
|
|
||||||
autoprefixer@10.4.21:
|
autoprefixer@10.4.21:
|
||||||
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@ -2091,32 +2081,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
|
|
||||||
color-name@1.1.3:
|
|
||||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
|
||||||
|
|
||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
color-string@1.9.1:
|
color-string@1.9.1:
|
||||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
|
||||||
color@3.2.1:
|
|
||||||
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
|
||||||
|
|
||||||
color@4.2.3:
|
color@4.2.3:
|
||||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
engines: {node: '>=12.5.0'}
|
engines: {node: '>=12.5.0'}
|
||||||
|
|
||||||
colorspace@1.1.4:
|
|
||||||
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
|
|
||||||
|
|
||||||
command-exists@1.2.9:
|
command-exists@1.2.9:
|
||||||
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
|
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
|
||||||
|
|
||||||
@ -2179,6 +2157,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
|
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
cron-parser@5.3.0:
|
||||||
|
resolution: {integrity: sha512-IS4mnFu6n3CFgEmXjr+B2zzGHsjJmHEdN+BViKvYSiEn3KWss9ICRDETDX/VZldiW82B94OyAZm4LIT4vcKK0g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
cron@4.3.2:
|
||||||
|
resolution: {integrity: sha512-JxBBnf5zRz+NhW9XcP16gwUKAKIimy2G0QCCQu8kk5XwM4aCGwMt+nntouAfXF9A57965XzB6hitBlJAz5Ts6w==}
|
||||||
|
engines: {node: '>=18.x'}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -2422,9 +2408,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
|
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
enabled@2.0.0:
|
|
||||||
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
|
||||||
|
|
||||||
encodeurl@1.0.2:
|
encodeurl@1.0.2:
|
||||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -2585,9 +2568,6 @@ packages:
|
|||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
fecha@4.2.3:
|
|
||||||
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
|
||||||
|
|
||||||
fetch-blob@3.2.0:
|
fetch-blob@3.2.0:
|
||||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||||
engines: {node: ^12.20 || >= 14.13}
|
engines: {node: ^12.20 || >= 14.13}
|
||||||
@ -2631,9 +2611,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
|
resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
fn.name@1.1.0:
|
|
||||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
|
||||||
|
|
||||||
follow-redirects@1.15.9:
|
follow-redirects@1.15.9:
|
||||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@ -2903,10 +2880,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
|
resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-stream@2.0.1:
|
|
||||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-stream@4.0.1:
|
is-stream@4.0.1:
|
||||||
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -2984,9 +2957,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
kuler@2.0.0:
|
|
||||||
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
|
||||||
|
|
||||||
lilconfig@3.1.3:
|
lilconfig@3.1.3:
|
||||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -3026,10 +2996,6 @@ packages:
|
|||||||
lodash@4.17.21:
|
lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
|
||||||
logform@2.7.0:
|
|
||||||
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
|
||||||
engines: {node: '>= 12.0.0'}
|
|
||||||
|
|
||||||
long@5.2.4:
|
long@5.2.4:
|
||||||
resolution: {integrity: sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==}
|
resolution: {integrity: sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==}
|
||||||
|
|
||||||
@ -3040,6 +3006,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
|
resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
|
|
||||||
|
luxon@3.7.1:
|
||||||
|
resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
lzma-native@8.0.6:
|
lzma-native@8.0.6:
|
||||||
resolution: {integrity: sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==}
|
resolution: {integrity: sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@ -3316,9 +3286,6 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
one-time@1.0.0:
|
|
||||||
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
|
||||||
|
|
||||||
os-tmpdir@1.0.2:
|
os-tmpdir@1.0.2:
|
||||||
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -3748,10 +3715,6 @@ packages:
|
|||||||
safe-json-stringify@1.2.0:
|
safe-json-stringify@1.2.0:
|
||||||
resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==}
|
resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==}
|
||||||
|
|
||||||
safe-stable-stringify@2.5.0:
|
|
||||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
@ -3868,9 +3831,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==}
|
resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==}
|
||||||
engines: {node: '>=10.16.0'}
|
engines: {node: '>=10.16.0'}
|
||||||
|
|
||||||
stack-trace@0.0.10:
|
|
||||||
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -4031,9 +3991,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
|
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
text-hex@1.0.0:
|
|
||||||
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
|
||||||
|
|
||||||
thenify-all@1.6.0:
|
thenify-all@1.6.0:
|
||||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
@ -4081,10 +4038,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
triple-beam@1.4.1:
|
|
||||||
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
|
||||||
engines: {node: '>= 14.0.0'}
|
|
||||||
|
|
||||||
ts-interface-checker@0.1.13:
|
ts-interface-checker@0.1.13:
|
||||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||||
|
|
||||||
@ -4319,14 +4272,6 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
winston-transport@4.9.0:
|
|
||||||
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
|
|
||||||
engines: {node: '>= 12.0.0'}
|
|
||||||
|
|
||||||
winston@3.17.0:
|
|
||||||
resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==}
|
|
||||||
engines: {node: '>= 12.0.0'}
|
|
||||||
|
|
||||||
with@7.0.2:
|
with@7.0.2:
|
||||||
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@ -4635,18 +4580,10 @@ snapshots:
|
|||||||
'@cloudflare/workerd-windows-64@1.20250712.0':
|
'@cloudflare/workerd-windows-64@1.20250712.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@colors/colors@1.6.0': {}
|
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.9
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
|
|
||||||
'@dabh/diagnostics@2.0.3':
|
|
||||||
dependencies:
|
|
||||||
colorspace: 1.1.4
|
|
||||||
enabled: 2.0.0
|
|
||||||
kuler: 2.0.0
|
|
||||||
|
|
||||||
'@emnapi/core@1.4.4':
|
'@emnapi/core@1.4.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.0.3
|
'@emnapi/wasi-threads': 1.0.3
|
||||||
@ -5586,6 +5523,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/lodash@4.17.14': {}
|
'@types/lodash@4.17.14': {}
|
||||||
|
|
||||||
|
'@types/luxon@3.6.2': {}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 2.0.11
|
'@types/unist': 2.0.11
|
||||||
@ -5648,8 +5587,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/tmp@0.0.33': {}
|
'@types/tmp@0.0.33': {}
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5': {}
|
|
||||||
|
|
||||||
'@types/unist@2.0.11': {}
|
'@types/unist@2.0.11': {}
|
||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
@ -5739,8 +5676,6 @@ snapshots:
|
|||||||
|
|
||||||
astral-regex@2.0.0: {}
|
astral-regex@2.0.0: {}
|
||||||
|
|
||||||
async@3.2.6: {}
|
|
||||||
|
|
||||||
autoprefixer@10.4.21(postcss@8.5.6):
|
autoprefixer@10.4.21(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.1
|
browserslist: 4.25.1
|
||||||
@ -5939,16 +5874,10 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
|
||||||
dependencies:
|
|
||||||
color-name: 1.1.3
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
|
|
||||||
color-name@1.1.3: {}
|
|
||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
color-string@1.9.1:
|
color-string@1.9.1:
|
||||||
@ -5956,21 +5885,11 @@ snapshots:
|
|||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
simple-swizzle: 0.2.2
|
simple-swizzle: 0.2.2
|
||||||
|
|
||||||
color@3.2.1:
|
|
||||||
dependencies:
|
|
||||||
color-convert: 1.9.3
|
|
||||||
color-string: 1.9.1
|
|
||||||
|
|
||||||
color@4.2.3:
|
color@4.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
color-string: 1.9.1
|
color-string: 1.9.1
|
||||||
|
|
||||||
colorspace@1.1.4:
|
|
||||||
dependencies:
|
|
||||||
color: 3.2.1
|
|
||||||
text-hex: 1.0.0
|
|
||||||
|
|
||||||
command-exists@1.2.9: {}
|
command-exists@1.2.9: {}
|
||||||
|
|
||||||
commander@12.1.0: {}
|
commander@12.1.0: {}
|
||||||
@ -6018,6 +5937,15 @@ snapshots:
|
|||||||
nan: 2.22.0
|
nan: 2.22.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
cron-parser@5.3.0:
|
||||||
|
dependencies:
|
||||||
|
luxon: 3.7.1
|
||||||
|
|
||||||
|
cron@4.3.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/luxon': 3.6.2
|
||||||
|
luxon: 3.7.1
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@ -6266,8 +6194,6 @@ snapshots:
|
|||||||
|
|
||||||
empathic@2.0.0: {}
|
empathic@2.0.0: {}
|
||||||
|
|
||||||
enabled@2.0.0: {}
|
|
||||||
|
|
||||||
encodeurl@1.0.2: {}
|
encodeurl@1.0.2: {}
|
||||||
|
|
||||||
encodeurl@2.0.0: {}
|
encodeurl@2.0.0: {}
|
||||||
@ -6494,8 +6420,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.2
|
picomatch: 4.0.2
|
||||||
|
|
||||||
fecha@4.2.3: {}
|
|
||||||
|
|
||||||
fetch-blob@3.2.0:
|
fetch-blob@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-domexception: 1.0.0
|
node-domexception: 1.0.0
|
||||||
@ -6545,8 +6469,6 @@ snapshots:
|
|||||||
path-exists: 5.0.0
|
path-exists: 5.0.0
|
||||||
unicorn-magic: 0.1.0
|
unicorn-magic: 0.1.0
|
||||||
|
|
||||||
fn.name@1.1.0: {}
|
|
||||||
|
|
||||||
follow-redirects@1.15.9(debug@4.4.0):
|
follow-redirects@1.15.9(debug@4.4.0):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
@ -6870,8 +6792,6 @@ snapshots:
|
|||||||
|
|
||||||
is-stream@1.1.0: {}
|
is-stream@1.1.0: {}
|
||||||
|
|
||||||
is-stream@2.0.1: {}
|
|
||||||
|
|
||||||
is-stream@4.0.1: {}
|
is-stream@4.0.1: {}
|
||||||
|
|
||||||
is-subdir@1.2.0:
|
is-subdir@1.2.0:
|
||||||
@ -6940,8 +6860,6 @@ snapshots:
|
|||||||
|
|
||||||
kleur@4.1.5: {}
|
kleur@4.1.5: {}
|
||||||
|
|
||||||
kuler@2.0.0: {}
|
|
||||||
|
|
||||||
lilconfig@3.1.3: {}
|
lilconfig@3.1.3: {}
|
||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
@ -6970,21 +6888,14 @@ snapshots:
|
|||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
|
||||||
logform@2.7.0:
|
|
||||||
dependencies:
|
|
||||||
'@colors/colors': 1.6.0
|
|
||||||
'@types/triple-beam': 1.3.5
|
|
||||||
fecha: 4.2.3
|
|
||||||
ms: 2.1.3
|
|
||||||
safe-stable-stringify: 2.5.0
|
|
||||||
triple-beam: 1.4.1
|
|
||||||
|
|
||||||
long@5.2.4: {}
|
long@5.2.4: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@11.0.2: {}
|
lru-cache@11.0.2: {}
|
||||||
|
|
||||||
|
luxon@3.7.1: {}
|
||||||
|
|
||||||
lzma-native@8.0.6:
|
lzma-native@8.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-addon-api: 3.2.1
|
node-addon-api: 3.2.1
|
||||||
@ -7318,10 +7229,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
one-time@1.0.0:
|
|
||||||
dependencies:
|
|
||||||
fn.name: 1.1.0
|
|
||||||
|
|
||||||
os-tmpdir@1.0.2: {}
|
os-tmpdir@1.0.2: {}
|
||||||
|
|
||||||
outdent@0.5.0: {}
|
outdent@0.5.0: {}
|
||||||
@ -7794,8 +7701,6 @@ snapshots:
|
|||||||
safe-json-stringify@1.2.0:
|
safe-json-stringify@1.2.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
safe-stable-stringify@2.5.0: {}
|
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
sass@1.89.2:
|
sass@1.89.2:
|
||||||
@ -7984,8 +7889,6 @@ snapshots:
|
|||||||
cpu-features: 0.0.10
|
cpu-features: 0.0.10
|
||||||
nan: 2.22.0
|
nan: 2.22.0
|
||||||
|
|
||||||
stack-trace@0.0.10: {}
|
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
stoppable@1.1.0: {}
|
stoppable@1.1.0: {}
|
||||||
@ -8175,8 +8078,6 @@ snapshots:
|
|||||||
|
|
||||||
term-size@2.2.1: {}
|
term-size@2.2.1: {}
|
||||||
|
|
||||||
text-hex@1.0.0: {}
|
|
||||||
|
|
||||||
thenify-all@1.6.0:
|
thenify-all@1.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
thenify: 3.3.1
|
thenify: 3.3.1
|
||||||
@ -8218,8 +8119,6 @@ snapshots:
|
|||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
triple-beam@1.4.1: {}
|
|
||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
tsdown@0.12.9(typescript@5.7.3):
|
tsdown@0.12.9(typescript@5.7.3):
|
||||||
@ -8418,26 +8317,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
winston-transport@4.9.0:
|
|
||||||
dependencies:
|
|
||||||
logform: 2.7.0
|
|
||||||
readable-stream: 3.6.2
|
|
||||||
triple-beam: 1.4.1
|
|
||||||
|
|
||||||
winston@3.17.0:
|
|
||||||
dependencies:
|
|
||||||
'@colors/colors': 1.6.0
|
|
||||||
'@dabh/diagnostics': 2.0.3
|
|
||||||
async: 3.2.6
|
|
||||||
is-stream: 2.0.1
|
|
||||||
logform: 2.7.0
|
|
||||||
one-time: 1.0.0
|
|
||||||
readable-stream: 3.6.2
|
|
||||||
safe-stable-stringify: 2.5.0
|
|
||||||
stack-trace: 0.0.10
|
|
||||||
triple-beam: 1.4.1
|
|
||||||
winston-transport: 4.9.0
|
|
||||||
|
|
||||||
with@7.0.2:
|
with@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.28.0
|
'@babel/parser': 7.28.0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user