From f768cb2bd3e34718b0767e663a1027af1ac89516 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 1 Nov 2026 19:50:05 +0200 Subject: [PATCH 01/14] Add SDK-aligned automation adapters for four platforms --- .github/workflows/check.yml | 27 + .github/workflows/publish-n8n.yml | 34 + .github/workflows/sdk-drift.yml | 16 + .npmrc | 2 + .nvmrc | 1 + CHANGELOG.md | 16 + MAINTAINING.md | 73 + NOTICE.md | 5 + README.md | 6 + contracts/coverage.json | 86 + contracts/openapi.messaging.json | 4661 ++++ contracts/openapi.platform.json | 1330 + contracts/sdk-baseline.json | 61 + contracts/source.json | 30 + fixtures/events.json | 27 + make/README.md | 36 + make/app.json | 8 + make/base.json | 20 + make/connections/project/communication.json | 23 + make/connections/project/parameters.json | 14 + make/functions/polymorfaAdvanced.js | 36 + make/functions/polymorfaBody.js | 57 + make/functions/polymorfaFindHook.js | 11 + make/functions/polymorfaHook.js | 26 + make/functions/polymorfaOwnedHook.js | 6 + make/functions/polymorfaReceive.js | 6 + make/functions/polymorfaRequest.js | 44 + make/functions/polymorfaResult.js | 25 + make/functions/polymorfaSegment.js | 20 + make/modules/apiRequest/communication.json | 19 + make/modules/apiRequest/interface.json | 7 + make/modules/apiRequest/metadata.json | 6 + make/modules/apiRequest/parameters.json | 48 + make/modules/callEnded/interface.json | 96 + make/modules/callEnded/metadata.json | 6 + make/modules/listNumbers/communication.json | 19 + make/modules/listNumbers/interface.json | 141 + make/modules/listNumbers/metadata.json | 6 + make/modules/listNumbers/parameters.json | 1 + make/modules/listTemplates/communication.json | 19 + make/modules/listTemplates/interface.json | 72 + make/modules/listTemplates/metadata.json | 6 + make/modules/listTemplates/parameters.json | 8 + make/modules/messageReceived/interface.json | 410 + make/modules/messageReceived/metadata.json | 6 + make/modules/messageState/interface.json | 602 + make/modules/messageState/metadata.json | 6 + make/modules/numberState/interface.json | 124 + make/modules/numberState/metadata.json | 6 + make/modules/react/communication.json | 19 + make/modules/react/interface.json | 78 + make/modules/react/metadata.json | 6 + make/modules/react/parameters.json | 64 + make/modules/sendMedia/communication.json | 19 + make/modules/sendMedia/interface.json | 669 + make/modules/sendMedia/metadata.json | 6 + make/modules/sendMedia/parameters.json | 119 + make/modules/sendTemplate/communication.json | 19 + make/modules/sendTemplate/interface.json | 669 + make/modules/sendTemplate/metadata.json | 6 + make/modules/sendTemplate/parameters.json | 72 + make/modules/sendText/communication.json | 19 + make/modules/sendText/interface.json | 669 + make/modules/sendText/metadata.json | 6 + make/modules/sendText/parameters.json | 64 + make/webhooks/callEnded/attach.json | 50 + make/webhooks/callEnded/communication.json | 15 + make/webhooks/callEnded/detach.json | 20 + make/webhooks/callEnded/metadata.json | 5 + make/webhooks/callEnded/parameters.json | 9 + make/webhooks/messageReceived/attach.json | 50 + .../messageReceived/communication.json | 15 + make/webhooks/messageReceived/detach.json | 20 + make/webhooks/messageReceived/metadata.json | 5 + make/webhooks/messageReceived/parameters.json | 9 + make/webhooks/messageState/attach.json | 53 + make/webhooks/messageState/communication.json | 15 + make/webhooks/messageState/detach.json | 20 + make/webhooks/messageState/metadata.json | 5 + make/webhooks/messageState/parameters.json | 9 + make/webhooks/numberState/attach.json | 52 + make/webhooks/numberState/communication.json | 15 + make/webhooks/numberState/detach.json | 20 + make/webhooks/numberState/metadata.json | 5 + make/webhooks/numberState/parameters.json | 9 + n8n/LICENSE | 21 + n8n/NOTICE.md | 5 + n8n/README.md | 40 + n8n/credentials/PolymorfaApi.credentials.ts | 14 + n8n/eslint.config.mjs | 2 + n8n/nodes/Polymorfa/Polymorfa.node.ts | 60 + n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts | 54 + n8n/nodes/Polymorfa/polymorfa.dark.svg | 3 + n8n/nodes/Polymorfa/polymorfa.light.svg | 3 + n8n/nodes/Polymorfa/transport.ts | 12 + n8n/package.json | 21 + n8n/shared/catalog.js | 43 + n8n/shared/core.js | 129 + n8n/shared/hooks.js | 92 + n8n/shared/samples.json | 65 + n8n/tsconfig.json | 10 + package-lock.json | 20169 ++++++++++++++++ package.json | 29 + pipedream/README.md | 28 + pipedream/actions/apiRequest/apiRequest.mjs | 53 + pipedream/actions/listNumbers/listNumbers.mjs | 11 + .../actions/listTemplates/listTemplates.mjs | 17 + pipedream/actions/react/react.mjs | 67 + pipedream/actions/sendMedia/sendMedia.mjs | 118 + .../actions/sendTemplate/sendTemplate.mjs | 74 + pipedream/actions/sendText/sendText.mjs | 67 + pipedream/common/catalog.cjs | 43 + pipedream/common/core.cjs | 129 + pipedream/common/hooks.cjs | 92 + pipedream/common/samples.json | 65 + pipedream/polymorfa.app.mjs | 24 + pipedream/sources/callEnded/callEnded.mjs | 25 + .../messageReceived/messageReceived.mjs | 25 + .../sources/messageState/messageState.mjs | 25 + pipedream/sources/numberState/numberState.mjs | 25 + recipes/README.md | 39 + recipes/field-maps.json | 15 + recipes/n8n/inbound-handoff.json | 10 + recipes/n8n/order-notification.json | 10 + releases/0.1.0.json | 15 + scripts/check-contracts.mjs | 27 + scripts/check-upstream.mjs | 52 + scripts/generate.mjs | 113 + scripts/openapi-schema.cjs | 15 + scripts/release-gate.mjs | 11 + scripts/update-sdk-baseline.mjs | 34 + scripts/verify-sdk.mjs | 27 + shared/catalog.cjs | 42 + shared/core.cjs | 128 + shared/hooks.cjs | 91 + tests/core.test.cjs | 78 + tests/hooks.test.cjs | 91 + tests/make.test.cjs | 41 + tests/native.test.cjs | 50 + tests/pipedream.test.mjs | 20 + zapier/README.md | 34 + zapier/index.js | 61 + zapier/package.json | 7 + zapier/shared/catalog.cjs | 43 + zapier/shared/core.cjs | 129 + zapier/shared/hooks.cjs | 92 + zapier/shared/samples.json | 65 + 147 files changed, 34220 insertions(+) create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/publish-n8n.yml create mode 100644 .github/workflows/sdk-drift.yml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 CHANGELOG.md create mode 100644 MAINTAINING.md create mode 100644 NOTICE.md create mode 100644 contracts/coverage.json create mode 100644 contracts/openapi.messaging.json create mode 100644 contracts/openapi.platform.json create mode 100644 contracts/sdk-baseline.json create mode 100644 contracts/source.json create mode 100644 fixtures/events.json create mode 100644 make/README.md create mode 100644 make/app.json create mode 100644 make/base.json create mode 100644 make/connections/project/communication.json create mode 100644 make/connections/project/parameters.json create mode 100644 make/functions/polymorfaAdvanced.js create mode 100644 make/functions/polymorfaBody.js create mode 100644 make/functions/polymorfaFindHook.js create mode 100644 make/functions/polymorfaHook.js create mode 100644 make/functions/polymorfaOwnedHook.js create mode 100644 make/functions/polymorfaReceive.js create mode 100644 make/functions/polymorfaRequest.js create mode 100644 make/functions/polymorfaResult.js create mode 100644 make/functions/polymorfaSegment.js create mode 100644 make/modules/apiRequest/communication.json create mode 100644 make/modules/apiRequest/interface.json create mode 100644 make/modules/apiRequest/metadata.json create mode 100644 make/modules/apiRequest/parameters.json create mode 100644 make/modules/callEnded/interface.json create mode 100644 make/modules/callEnded/metadata.json create mode 100644 make/modules/listNumbers/communication.json create mode 100644 make/modules/listNumbers/interface.json create mode 100644 make/modules/listNumbers/metadata.json create mode 100644 make/modules/listNumbers/parameters.json create mode 100644 make/modules/listTemplates/communication.json create mode 100644 make/modules/listTemplates/interface.json create mode 100644 make/modules/listTemplates/metadata.json create mode 100644 make/modules/listTemplates/parameters.json create mode 100644 make/modules/messageReceived/interface.json create mode 100644 make/modules/messageReceived/metadata.json create mode 100644 make/modules/messageState/interface.json create mode 100644 make/modules/messageState/metadata.json create mode 100644 make/modules/numberState/interface.json create mode 100644 make/modules/numberState/metadata.json create mode 100644 make/modules/react/communication.json create mode 100644 make/modules/react/interface.json create mode 100644 make/modules/react/metadata.json create mode 100644 make/modules/react/parameters.json create mode 100644 make/modules/sendMedia/communication.json create mode 100644 make/modules/sendMedia/interface.json create mode 100644 make/modules/sendMedia/metadata.json create mode 100644 make/modules/sendMedia/parameters.json create mode 100644 make/modules/sendTemplate/communication.json create mode 100644 make/modules/sendTemplate/interface.json create mode 100644 make/modules/sendTemplate/metadata.json create mode 100644 make/modules/sendTemplate/parameters.json create mode 100644 make/modules/sendText/communication.json create mode 100644 make/modules/sendText/interface.json create mode 100644 make/modules/sendText/metadata.json create mode 100644 make/modules/sendText/parameters.json create mode 100644 make/webhooks/callEnded/attach.json create mode 100644 make/webhooks/callEnded/communication.json create mode 100644 make/webhooks/callEnded/detach.json create mode 100644 make/webhooks/callEnded/metadata.json create mode 100644 make/webhooks/callEnded/parameters.json create mode 100644 make/webhooks/messageReceived/attach.json create mode 100644 make/webhooks/messageReceived/communication.json create mode 100644 make/webhooks/messageReceived/detach.json create mode 100644 make/webhooks/messageReceived/metadata.json create mode 100644 make/webhooks/messageReceived/parameters.json create mode 100644 make/webhooks/messageState/attach.json create mode 100644 make/webhooks/messageState/communication.json create mode 100644 make/webhooks/messageState/detach.json create mode 100644 make/webhooks/messageState/metadata.json create mode 100644 make/webhooks/messageState/parameters.json create mode 100644 make/webhooks/numberState/attach.json create mode 100644 make/webhooks/numberState/communication.json create mode 100644 make/webhooks/numberState/detach.json create mode 100644 make/webhooks/numberState/metadata.json create mode 100644 make/webhooks/numberState/parameters.json create mode 100644 n8n/LICENSE create mode 100644 n8n/NOTICE.md create mode 100644 n8n/README.md create mode 100644 n8n/credentials/PolymorfaApi.credentials.ts create mode 100644 n8n/eslint.config.mjs create mode 100644 n8n/nodes/Polymorfa/Polymorfa.node.ts create mode 100644 n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts create mode 100644 n8n/nodes/Polymorfa/polymorfa.dark.svg create mode 100644 n8n/nodes/Polymorfa/polymorfa.light.svg create mode 100644 n8n/nodes/Polymorfa/transport.ts create mode 100644 n8n/package.json create mode 100644 n8n/shared/catalog.js create mode 100644 n8n/shared/core.js create mode 100644 n8n/shared/hooks.js create mode 100644 n8n/shared/samples.json create mode 100644 n8n/tsconfig.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pipedream/README.md create mode 100644 pipedream/actions/apiRequest/apiRequest.mjs create mode 100644 pipedream/actions/listNumbers/listNumbers.mjs create mode 100644 pipedream/actions/listTemplates/listTemplates.mjs create mode 100644 pipedream/actions/react/react.mjs create mode 100644 pipedream/actions/sendMedia/sendMedia.mjs create mode 100644 pipedream/actions/sendTemplate/sendTemplate.mjs create mode 100644 pipedream/actions/sendText/sendText.mjs create mode 100644 pipedream/common/catalog.cjs create mode 100644 pipedream/common/core.cjs create mode 100644 pipedream/common/hooks.cjs create mode 100644 pipedream/common/samples.json create mode 100644 pipedream/polymorfa.app.mjs create mode 100644 pipedream/sources/callEnded/callEnded.mjs create mode 100644 pipedream/sources/messageReceived/messageReceived.mjs create mode 100644 pipedream/sources/messageState/messageState.mjs create mode 100644 pipedream/sources/numberState/numberState.mjs create mode 100644 recipes/README.md create mode 100644 recipes/field-maps.json create mode 100644 recipes/n8n/inbound-handoff.json create mode 100644 recipes/n8n/order-notification.json create mode 100644 releases/0.1.0.json create mode 100644 scripts/check-contracts.mjs create mode 100644 scripts/check-upstream.mjs create mode 100644 scripts/generate.mjs create mode 100644 scripts/openapi-schema.cjs create mode 100644 scripts/release-gate.mjs create mode 100644 scripts/update-sdk-baseline.mjs create mode 100644 scripts/verify-sdk.mjs create mode 100644 shared/catalog.cjs create mode 100644 shared/core.cjs create mode 100644 shared/hooks.cjs create mode 100644 tests/core.test.cjs create mode 100644 tests/hooks.test.cjs create mode 100644 tests/make.test.cjs create mode 100644 tests/native.test.cjs create mode 100644 tests/pipedream.test.mjs create mode 100644 zapier/README.md create mode 100644 zapier/index.js create mode 100644 zapier/package.json create mode 100644 zapier/shared/catalog.cjs create mode 100644 zapier/shared/core.cjs create mode 100644 zapier/shared/hooks.cjs create mode 100644 zapier/shared/samples.json diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..75d6df3 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,27 @@ +name: Integration checks +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +jobs: + contracts-and-adapters: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + cache: npm + - run: npm install --global npm@11.20.0 + - run: npm ci --ignore-scripts --no-audit + - run: npm run check + - run: npm run lint --workspace n8n-nodes-polymorfa + - run: npm run validate:zapier + - run: npm run check:sdk + - run: node scripts/check-upstream.mjs --pinned + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22.23.2' + - run: node --test --test-reporter=spec tests/*.test.cjs tests/*.test.mjs diff --git a/.github/workflows/publish-n8n.yml b/.github/workflows/publish-n8n.yml new file mode 100644 index 0000000..9c4a630 --- /dev/null +++ b/.github/workflows/publish-n8n.yml @@ -0,0 +1,34 @@ +name: Publish n8n package +on: + workflow_dispatch: + inputs: + version: + description: Reviewed package version + required: true + type: string +permissions: + contents: read +jobs: + publish: + runs-on: ubuntu-latest + environment: npm-release + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + registry-url: https://registry.npmjs.org + - run: npm install --global npm@11.20.0 + - run: npm ci --ignore-scripts --no-audit + - run: npm run check + - run: npm run lint --workspace n8n-nodes-polymorfa + - run: npm run check:sdk + - run: node scripts/release-gate.mjs + env: + RELEASE_VERSION: ${{ inputs.version }} + - run: npm publish --workspace n8n-nodes-polymorfa --provenance --access public diff --git a/.github/workflows/sdk-drift.yml b/.github/workflows/sdk-drift.yml new file mode 100644 index 0000000..9e3fa13 --- /dev/null +++ b/.github/workflows/sdk-drift.yml @@ -0,0 +1,16 @@ +name: SDK drift +on: + workflow_dispatch: + schedule: + - cron: '23 8 * * 1' +permissions: + contents: read +jobs: + check-reviewed-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + - run: node scripts/check-upstream.mjs diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7833b32 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +save-exact=true +fund=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..5bf4400 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.15.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9fb61bd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## Unreleased + +- First-party source for n8n, Make, Zapier and Pipedream with a common public API + and SDK contract pin. +- Text/replies, templates, URL/base64 media, reactions, Number/template lookup + and allowlisted API calls. n8n also maps native binary inputs. +- Owned project-webhook subscriptions for messages, message state, Number state + and completed calls, with authenticated receivers and stable event identity. +- Explicit recovery for completed/uncertain writes and conservative retry age. +- Contract/generation checks, SDK drift detection and provider release records. + +This is development source. No registry publication, provider approval or public +marketplace listing is established. Functions and message-operation lookup are +excluded until their public contracts are accepted. diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 0000000..942e4ba --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,73 @@ +# Maintain the integrations with the SDKs + +Use Node 24.15.0 and npm 11.20.0 for the build tools. The generated JavaScript is +also checked on Node 22.23.2 for the Zapier execution boundary. These checks do +not establish the versions deployed by a provider. + +```sh +npm ci --ignore-scripts +npm run check +npm run lint --workspace n8n-nodes-polymorfa +npm run validate:zapier +npm run check:sdk +node scripts/check-upstream.mjs --pinned +``` + +`shared/` owns the action fields, event groups, request mapping and recovery +behavior. `npm run generate` copies that code into independently deployable +native packages and generates Make functions and Pipedream components. Do not +edit generated files directly. CI rejects stale generated output. + +`contracts/source.json` pins the public API audience and matching SDK source. +`coverage.json` identifies each allowed operation and deliberate exclusions. +The public snapshots contain only these operations and their referenced schemas. +Do not import the complete management API or an SDK's extra operation catalog. + +The SDK's baseline also contains fields outside this public audience, including +transport selection. The adapters do not expose them. `check:sdk` validates the +actual request cases against the pinned SDK schemas. `sdk-baseline.json` records +the corresponding reviewed SDK fragment fingerprints. `check:upstream` compares +them to current SDK `main`; its scheduled workflow reports changes without +repinning, publishing or creating an app. The pinned check is reproducible even +when SDK `main` advances. + +For a relevant API/SDK change, update the accepted public snapshot, source/hash +metadata, coverage and every affected adapter. Add an exclusion with its reason +when a capability is intentionally unavailable. Test errors, IDs, media, hooks, +pagination and uncertain writes where affected. Regenerate all packages and +update native recipe mappings. Keep one integrations PR for that feature cycle. + +Record real provider tests separately from these local fixtures. Include exact +source/app versions and the accepted API revision. Update the owning product +feature, issue, release record, customer documentation and provider submission. +Source merge, package publication, app review and public listing are distinct. + +The n8n publication workflow is manual, uses npm provenance and requires recorded +receipts. Configure a protected `npm-release` environment and a Polymorfa-owned +npm trusted publisher before use. No provider account, environment protection or +trusted publisher has been configured by adding this workflow. Review is still +required; the gate is bookkeeping and does not authenticate human authorization. + +Keep API credentials in each provider's credential store. Webhook receivers use +an authenticated per-subscription header supported by the public subscription +contract. A stable unique subscription key determines its private verifier. +Reactivation reuses its owned hook; another callback cannot reuse the key while +that hook exists. Changing a verifier or connection requires explicit lifecycle +and rotation testing. The adapters never emit signing secrets or add a relay. + +All delivery channels can repeat events. Use the event ID in downstream dedupe +storage and a stable business key for effects. Zapier and Pipedream also use that +event ID in their native dedupe paths. No unbounded exactly-once claim is made. +Native n8n and Make consumers must persist their own processed-event IDs. + +Revoking an API key does not delete an existing webhook endpoint. Disable/delete +the owned endpoint before withdrawing a connection, or remove it through an +authorized project management path if the key is already revoked. Never use a +broader credential automatically. Verify provider log redaction and callback +cleanup during acceptance, including failed cleanup and credential rotation. + +Make uses provider-native custom IML functions, which require enablement by Make. +The generated JavaScript functions are tested, but their IML bindings, private +webhook-state mapping and attach/detach behavior require real platform acceptance. +Its hook preflight refuses inventories larger than one page instead of risking +a duplicate. Qualify pagination in Make before approving large-project triggers. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..9f55905 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,5 @@ +The Polymorfa marks identify Polymorfa's service. The n8n icon uses the approved +Polymorfa website favicon. Trademark rights are separate from the MIT code license. + +n8n, Make, Zapier and Pipedream are names of their respective providers. This +development repository does not claim provider certification or approval. diff --git a/README.md b/README.md index d4e98b1..7d97f53 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,11 @@ a separate workflow engine or stores your third-party app connections. Documentation: [docs.polymorfa.com](https://docs.polymorfa.com). +Build and maintenance: [MAINTAINING.md](MAINTAINING.md). +Provider source: [n8n](n8n/README.md), [Make](make/README.md), +[Zapier](zapier/README.md), [Pipedream](pipedream/README.md). +[Recipe candidates](recipes/README.md) and [release state](releases/0.1.0.json) +record the remaining native acceptance and publication work. + License: MIT. Newly authored connector code only; no archived Titan adapter code is included. diff --git a/contracts/coverage.json b/contracts/coverage.json new file mode 100644 index 0000000..fb67d96 --- /dev/null +++ b/contracts/coverage.json @@ -0,0 +1,86 @@ +{ + "operations": { + "sendMessage": { + "contract": "messaging", + "method": "POST", + "path": "/messaging/{session}/messages/send", + "scope": "messages:write", + "sdkOperation": "sendMessage", + "purpose": "action" + }, + "sendReaction": { + "contract": "messaging", + "method": "POST", + "path": "/messaging/{session}/messages/react", + "scope": "messages:write", + "sdkOperation": "sendReaction", + "purpose": "action" + }, + "listProjectTemplates": { + "contract": "messaging", + "method": "GET", + "path": "/messaging/projects/{projectSlug}/templates", + "scope": "templates:read", + "sdkOperation": "listProjectTemplates", + "purpose": "action" + }, + "getProjectTemplate": { + "contract": "messaging", + "method": "GET", + "path": "/messaging/projects/{projectSlug}/templates/{id}", + "scope": "templates:read", + "sdkOperation": "getProjectTemplate", + "purpose": "action" + }, + "listPlatformSessions": { + "contract": "platform", + "method": "GET", + "path": "/platform/sessions", + "scope": "sessions:read", + "sdkOperation": "listPlatformSessions", + "purpose": "action" + }, + "listProjectWebhooks": { + "contract": "platform", + "method": "GET", + "path": "/platform/projects/{projectId}/webhooks", + "scope": "webhooks:read", + "sdkOperation": "listProjectWebhooks", + "purpose": "subscription lifecycle" + }, + "createProjectWebhook": { + "contract": "platform", + "method": "POST", + "path": "/platform/projects/{projectId}/webhooks", + "scope": "webhooks:manage", + "sdkOperation": "createProjectWebhook", + "purpose": "subscription lifecycle" + }, + "getProjectWebhook": { + "contract": "platform", + "method": "GET", + "path": "/platform/projects/{projectId}/webhooks/{webhookId}", + "scope": "webhooks:read", + "sdkOperation": "getProjectWebhook", + "purpose": "subscription lifecycle" + }, + "deleteProjectWebhook": { + "contract": "platform", + "method": "DELETE", + "path": "/platform/projects/{projectId}/webhooks/{webhookId}", + "scope": "webhooks:manage", + "sdkOperation": "deleteProjectWebhook", + "purpose": "subscription lifecycle" + } + }, + "platforms": [ + "n8n", + "make", + "zapier", + "pipedream" + ], + "excluded": { + "getMessageOperation": "Absent from published public Messaging export; SDK presence is insufficient.", + "runFunction": "Functions deliberately absent from published public Platform export." + } +} diff --git a/contracts/openapi.messaging.json b/contracts/openapi.messaging.json new file mode 100644 index 0000000..da75fdb --- /dev/null +++ b/contracts/openapi.messaging.json @@ -0,0 +1,4661 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Polymorfa automation messaging", + "version": "2026-09-22" + }, + "servers": [ + { + "url": "https://api.polymorfa.com" + } + ], + "paths": { + "/messaging/{session}/messages/send": { + "post": { + "operationId": "sendMessage", + "tags": [ + "Messages" + ], + "summary": "Send a message", + "description": "**Protocols:** Linked Device API · Meta Cloud API\n\n**Required scope:** `messages:write`\n\nSend a message to a conversation. Put exactly one message kind inside content. Testing sessions send only to active testing numbers in the same project; supply the recipient phoneNumber.", + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "messages:write", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Unique key, 1 to 255 bytes of UTF-8, that makes this write safe to retry for 24 hours. A retry with the same key, credential, `Polymorfa-Version`, and request never runs the write again: it returns `409 idempotency_completed` when the original succeeded, or the original server error, with `Idempotent-Replayed: true`. Reusing the key for a different request returns `409 idempotency_conflict`; a retry while the original is still running returns `409 idempotency_in_progress`, and a retry after an unrecorded outcome returns `409 idempotency_outcome_unknown`.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendMessageRequest" + }, + "example": { + "conversation": { + "phoneNumber": "+15550001111" + }, + "content": { + "text": "Hello" + } + } + } + } + }, + "responses": { + "200": { + "description": "Message sent successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendMessageResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "202": { + "description": "Accepted for asynchronous processing in response to `Prefer: respond-async`; the result is not included", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AsyncAcceptedResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "401": { + "description": "Invalid credential", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "402": { + "description": "`billing_credit_required`: credit is unavailable for this number. Add credit or review team billing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "billing_error", + "code": "billing_credit_required", + "message": "Credit is unavailable for this number. Add credit or review team billing.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#billing-credit-required" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Missing scope or resource access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "409": { + "description": "`unsupported_for_connection`: call permission requests need a Cloud API number; `call_permission_granted`: the person already granted permanent permission. The Idempotency-Key already succeeded (`idempotency_completed`), was used for a different request (`idempotency_conflict`), belongs to a request that is still running (`idempotency_in_progress`, with Retry-After), or belongs to a request whose outcome was not recorded (`idempotency_outcome_unknown`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "conflict_error", + "code": "unsupported_for_connection", + "message": "The session's connection does not support this call operation.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#unsupported-for-connection" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying `idempotency_in_progress`.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "Idempotent-Replayed": { + "description": "`true` when this response reports the recorded outcome of an earlier request with the same Idempotency-Key.", + "schema": { + "type": "string", + "enum": [ + "true" + ] + } + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "422": { + "description": "The supplied media URL could not be fetched", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "429": { + "description": "`call_permission_request_limited`: WhatsApp limits how often this person can be asked for call permission.", + "headers": { + "Retry-After": { + "description": "Seconds until the limiting window resets, when WhatsApp reports a reset time.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "Polymorfa-RateLimit-Reason": { + "description": "The limit that blocked the request.", + "schema": { + "type": "string", + "enum": [ + "call_permission_request" + ] + } + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "rate_limit_error", + "code": "call_permission_request_limited", + "message": "WhatsApp limits how often this number can ask this person for call permission. Follow Retry-After when supplied.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#call-permission-request-limited" + } + } + } + }, + "500": { + "description": "Unexpected service failure", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "501": { + "description": "`operation_not_supported`: the number's connection cannot send this content, such as a poll, or a URL, call or copy-code button on an official WhatsApp Business number", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "operation_not_supported", + "message": "This operation is not supported.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#operation-not-supported" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "502": { + "description": "Messaging provider failure", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "503": { + "description": "Session or service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "504": { + "description": "Result unknown after timeout", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + } + }, + "x-mint": { + "content": "Linked Device API Meta Cloud API messages:write" + }, + "x-protocols": [ + "Linked Device API", + "Meta Cloud API" + ] + } + }, + "/messaging/{session}/messages/react": { + "post": { + "operationId": "sendReaction", + "tags": [ + "Messages" + ], + "summary": "React to a message", + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "messages:write", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Unique key, 1 to 255 bytes of UTF-8, that makes this write safe to retry for 24 hours. A retry with the same key, credential, `Polymorfa-Version`, and request never runs the write again: it returns `409 idempotency_completed` when the original succeeded, or the original server error, with `Idempotent-Replayed: true`. Reusing the key for a different request returns `409 idempotency_conflict`; a retry while the original is still running returns `409 idempotency_in_progress`, and a retry after an unrecorded outcome returns `409 idempotency_outcome_unknown`.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReactRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Reaction sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendReactionResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "202": { + "description": "Accepted for asynchronous processing in response to `Prefer: respond-async`; the result is not included", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AsyncAcceptedResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "401": { + "description": "Invalid credential", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "402": { + "description": "`billing_credit_required`: credit is unavailable for this number. Add credit or review team billing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "billing_error", + "code": "billing_credit_required", + "message": "Credit is unavailable for this number. Add credit or review team billing.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#billing-credit-required" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Missing scope or resource access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "409": { + "description": "The Idempotency-Key already succeeded (`idempotency_completed`), was used for a different request (`idempotency_conflict`), belongs to a request that is still running (`idempotency_in_progress`, with Retry-After), or belongs to a request whose outcome was not recorded (`idempotency_outcome_unknown`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "conflict_error", + "code": "idempotency_conflict", + "message": "This Idempotency-Key was already used with a different request. Use a new key for a different request.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#idempotency-conflict" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying `idempotency_in_progress`.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "Idempotent-Replayed": { + "description": "`true` when this response reports the recorded outcome of an earlier request with the same Idempotency-Key.", + "schema": { + "type": "string", + "enum": [ + "true" + ] + } + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "500": { + "description": "Unexpected service failure", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "502": { + "description": "Messaging provider failure", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "503": { + "description": "Session or service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "504": { + "description": "Result unknown after timeout", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + } + }, + "x-mint": { + "content": "Linked Device API Meta Cloud API messages:write" + }, + "x-protocols": [ + "Linked Device API", + "Meta Cloud API" + ], + "description": "**Protocols:** Linked Device API · Meta Cloud API\n\n**Required scope:** `messages:write`" + } + }, + "/messaging/projects/{projectSlug}/templates": { + "get": { + "operationId": "listProjectTemplates", + "tags": [ + "Templates" + ], + "summary": "List project message templates", + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "templates:read", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "projectSlug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Templates", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListProjectTemplatesResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "401": { + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + } + }, + "x-mint": { + "content": "templates:read" + }, + "description": "**Required scope:** `templates:read`" + } + }, + "/messaging/projects/{projectSlug}/templates/{id}": { + "get": { + "operationId": "getProjectTemplate", + "tags": [ + "Templates" + ], + "summary": "Get a project message template", + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "templates:read", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "projectSlug", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Template", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectTemplateResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "401": { + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + } + }, + "x-mint": { + "content": "templates:read" + }, + "description": "**Required scope:** `templates:read`" + } + } + }, + "components": { + "schemas": { + "MessageReceivedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "message.received" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/MessageReceivedPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "MessageSentEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "message.sent" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/MessageSentPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "MessageAckEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "message.ack" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/MessageAckPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "MessageFailedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "message.failed" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/MessageFailedPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "MessageUpdateEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "message.update" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/MessagePayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "SessionStatusEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "session.status" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/SessionStatusPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "SessionConnectedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "session.connected" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/SessionConnectedPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "SessionLoggedOutEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "session.logged_out" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/SessionLoggedOutPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "CallEndedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique event identifier", + "example": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71" + }, + "session": { + "type": "string", + "description": "Session that produced this event", + "example": "739182640518203" + }, + "externalId": { + "type": "string", + "description": "Integrator reference from the QuickLink that created this session. Included on session lifecycle events when supplied." + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp", + "example": "2026-10-19T14:03:22.000Z" + }, + "event": { + "type": "string", + "enum": [ + "call.ended" + ], + "description": "Event type" + }, + "payload": { + "$ref": "#/components/schemas/CallEndedPayload" + } + }, + "required": [ + "id", + "session", + "timestamp", + "event", + "payload" + ] + }, + "SendMessageRequest": { + "type": "object", + "properties": { + "conversation": { + "$ref": "#/components/schemas/ConversationReference" + }, + "isForwarded": { + "type": "boolean", + "description": "Mark this message as forwarded." + }, + "mentions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "maxItems": 256, + "description": "User IDs to mention in this message." + }, + "quotedMessage": { + "$ref": "#/components/schemas/QuotedMessage" + }, + "content": { + "$ref": "#/components/schemas/MessageContent" + } + }, + "required": [ + "conversation", + "content" + ], + "additionalProperties": false, + "description": "Specify conversation and content containing exactly one message kind. Shared options remain outside content." + }, + "ConversationReference": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "phoneNumber": { + "type": "string", + "pattern": "^\\+[1-9]\\d{1,14}$", + "description": "International phone number to look up, including + and the country code. WhatsApp determines the recipient account." + }, + "bsuid": { + "type": "string", + "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" + }, + "username": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "Known username. This field alone does not select a conversation." + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "id" + ] + }, + { + "required": [ + "phoneNumber" + ] + }, + { + "required": [ + "bsuid" + ] + } + ] + }, + "QuotedMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "type": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "required": [ + "id" + ], + "description": "Message being replied to." + }, + "MessageContent": { + "description": "Exactly one message kind: text, image, location, or another supported content field.", + "oneOf": [ + { + "type": "object", + "properties": { + "text": { + "type": "string", + "minLength": 1, + "description": "Text to send." + } + }, + "required": [ + "text" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "image": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the media to send. Specify exactly one of url, base64 or descriptor." + }, + "base64": { + "type": "string", + "minLength": 1, + "description": "Base64-encoded media bytes. Specify exactly one of url, base64 or descriptor." + }, + "mimeType": { + "type": "string", + "description": "MIME type of the media. With descriptor, set descriptor.mimeType instead." + }, + "caption": { + "type": "string", + "description": "Caption sent with the media." + }, + "descriptor": { + "$ref": "#/components/schemas/ImageMediaDescriptor" + }, + "quality": { + "type": "string", + "enum": [ + "standard", + "hd" + ], + "description": "`standard` (default) sends the image once, as supplied. `hd` requests a WhatsApp HD pair: a standard-quality message followed by the original. HD pairs are not sent yet; an `hd` request is sent once, as supplied, and the response has no `hdMessageId`." + } + }, + "additionalProperties": false + } + }, + "required": [ + "image" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "video": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the media to send. Specify exactly one of url, base64 or descriptor." + }, + "base64": { + "type": "string", + "minLength": 1, + "description": "Base64-encoded media bytes. Specify exactly one of url, base64 or descriptor." + }, + "mimeType": { + "type": "string", + "description": "MIME type of the media. With descriptor, set descriptor.mimeType instead." + }, + "caption": { + "type": "string", + "description": "Caption sent with the media." + }, + "thumbnail": { + "type": "string", + "minLength": 4, + "maxLength": 21848, + "description": "Base64-encoded JPEG preview shown before the media downloads, at most 16 KiB. Linked Devices numbers only." + }, + "descriptor": { + "$ref": "#/components/schemas/VideoMediaDescriptor" + } + }, + "additionalProperties": false + } + }, + "required": [ + "video" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "file": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the media to send. Specify exactly one of url, base64 or descriptor." + }, + "base64": { + "type": "string", + "minLength": 1, + "description": "Base64-encoded media bytes. Specify exactly one of url, base64 or descriptor." + }, + "mimeType": { + "type": "string", + "description": "MIME type of the media. With descriptor, set descriptor.mimeType instead." + }, + "caption": { + "type": "string", + "description": "Caption sent with the media." + }, + "filename": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "minLength": 4, + "maxLength": 21848, + "description": "Base64-encoded JPEG preview shown before the media downloads, at most 16 KiB. Linked Devices numbers only." + }, + "descriptor": { + "$ref": "#/components/schemas/FileMediaDescriptor" + } + }, + "additionalProperties": false + } + }, + "required": [ + "file" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "voice": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the media to send. Specify exactly one of url, base64 or descriptor." + }, + "base64": { + "type": "string", + "minLength": 1, + "description": "Base64-encoded media bytes. Specify exactly one of url, base64 or descriptor." + }, + "mimeType": { + "type": "string", + "description": "MIME type of the media. With descriptor, set descriptor.mimeType instead." + }, + "caption": { + "type": "string", + "description": "Caption sent with the media." + }, + "ptt": { + "type": "boolean" + }, + "descriptor": { + "$ref": "#/components/schemas/VoiceMediaDescriptor" + } + }, + "additionalProperties": false + } + }, + "required": [ + "voice" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "sticker": { + "type": "object", + "properties": { + "descriptor": { + "$ref": "#/components/schemas/StickerMediaDescriptor" + } + }, + "required": [ + "descriptor" + ], + "additionalProperties": false + } + }, + "required": [ + "sticker" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "lat": { + "type": "number", + "minimum": -90, + "maximum": 90, + "description": "Latitude in decimal degrees." + }, + "long": { + "type": "number", + "minimum": -180, + "maximum": 180, + "description": "Longitude in decimal degrees." + }, + "address": { + "type": "string", + "description": "Display address for the location." + } + }, + "required": [ + "lat", + "long" + ], + "additionalProperties": false + } + }, + "required": [ + "location" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "poll": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "options": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 2, + "maxItems": 12 + }, + "multiSelect": { + "type": "boolean" + } + }, + "required": [ + "title", + "options" + ], + "additionalProperties": false + } + }, + "required": [ + "poll" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "contact": { + "type": "object", + "properties": { + "vcard": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "vcard" + ], + "additionalProperties": false + } + }, + "required": [ + "contact" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "requestPhoneNumber": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "requestPhoneNumber" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "product": { + "type": "object", + "properties": { + "businessOwnerId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 0 + }, + "currencyCode": { + "type": "string", + "pattern": "^[A-Z]{3}$" + }, + "priceAmount1000": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "salePriceAmount1000": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "retailerId": { + "type": "string", + "minLength": 0 + }, + "url": { + "type": "string", + "maxLength": 2048, + "format": "uri" + }, + "imageCount": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "image": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 4096, + "format": "uri" + }, + "base64": { + "type": "string", + "maxLength": 87384 + }, + "mimeType": { + "type": "string", + "minLength": 0 + } + } + }, + "body": { + "type": "string", + "minLength": 0 + }, + "footer": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "businessOwnerId", + "id", + "title", + "currencyCode", + "priceAmount1000" + ] + } + }, + "required": [ + "product" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "productList": { + "type": "object", + "properties": { + "businessOwnerId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 0 + }, + "buttonText": { + "type": "string", + "minLength": 1 + }, + "footer": { + "type": "string", + "minLength": 0 + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 0 + }, + "productIds": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 30 + } + }, + "required": [ + "productIds" + ] + }, + "minItems": 1, + "maxItems": 10 + } + }, + "required": [ + "businessOwnerId", + "title", + "buttonText", + "sections" + ] + } + }, + "required": [ + "productList" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "order": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "thumbnailBase64": { + "type": "string", + "maxLength": 87384 + }, + "itemCount": { + "type": "integer", + "minimum": 1, + "maximum": 100 + }, + "status": { + "type": "string", + "enum": [ + "inquiry", + "accepted", + "declined" + ] + }, + "message": { + "type": "string", + "minLength": 0 + }, + "title": { + "type": "string", + "minLength": 0 + }, + "sellerId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "token": { + "type": "string", + "minLength": 0 + }, + "totalAmount1000": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "totalCurrencyCode": { + "type": "string", + "pattern": "^[A-Z]{3}$" + }, + "catalogType": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "id", + "itemCount", + "status", + "sellerId", + "totalAmount1000", + "totalCurrencyCode" + ] + } + }, + "required": [ + "order" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "list": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 0 + }, + "buttonText": { + "type": "string", + "minLength": 1 + }, + "footer": { + "type": "string", + "minLength": 0 + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 0 + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "id", + "title" + ] + }, + "minItems": 1, + "maxItems": 30 + } + }, + "required": [ + "rows" + ] + }, + "minItems": 1, + "maxItems": 10 + } + }, + "required": [ + "title", + "buttonText", + "sections" + ], + "description": "On an official WhatsApp Business number, `title` is the header (at most 60 characters) and `description` the body (at most 1,024 characters); without `description`, `title` is the body. There, each section title allows at most 24 characters and is required when the list has more than one section, lists allow at most 10 rows in total, row titles at most 24 characters, row IDs at most 200, row descriptions at most 72, `buttonText` at most 20 and `footer` at most 60. Every number also limits `buttonText` to 64 UTF-8 bytes, row `id` to 256 bytes and `title` and `description` to 256 and 4,096 bytes, so multibyte text reaches those byte limits before the character limits. Content outside any limit returns `400`." + } + }, + "required": [ + "list" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "buttons": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 0 + }, + "body": { + "type": "string", + "minLength": 1 + }, + "footer": { + "type": "string", + "minLength": 0 + }, + "buttons": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "url" + ] + }, + "text": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "maxLength": 2048, + "format": "uri" + } + }, + "required": [ + "type", + "text", + "url" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "call" + ] + }, + "text": { + "type": "string", + "minLength": 1 + }, + "phoneNumber": { + "type": "string", + "pattern": "^\\+?\\d{6,20}$" + } + }, + "required": [ + "type", + "text", + "phoneNumber" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "reply" + ] + }, + "text": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "type", + "text", + "id" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "copy" + ] + }, + "text": { + "type": "string", + "minLength": 1 + }, + "copyCode": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "type", + "text", + "copyCode" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "catalog" + ] + }, + "text": { + "type": "string", + "minLength": 1 + }, + "businessPhoneNumber": { + "type": "string", + "pattern": "^\\+?\\d{6,20}$" + }, + "catalogProductId": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "type", + "text", + "businessPhoneNumber" + ] + } + ] + }, + "minItems": 1, + "maxItems": 3 + } + }, + "required": [ + "body", + "buttons" + ], + "description": "On an official WhatsApp Business number, only `reply` buttons are sent; other button types return `501`. There, `body` allows at most 1,024 characters, `title` and `footer` at most 60, and each button needs a unique `id` and a unique `text` of at most 20 characters. Every number also limits each button `id` to 256 UTF-8 bytes. Content outside any limit returns `400`." + } + }, + "required": [ + "buttons" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "addressMessage": { + "type": "object", + "properties": { + "body": { + "type": "string", + "minLength": 1 + }, + "buttonText": { + "type": "string", + "minLength": 1 + }, + "footer": { + "type": "string", + "minLength": 0 + }, + "country": { + "type": "string", + "pattern": "^[A-Z]{2}$" + } + }, + "required": [ + "body" + ] + } + }, + "required": [ + "addressMessage" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "flow": { + "type": "object", + "properties": { + "body": { + "type": "string", + "minLength": 1 + }, + "buttonText": { + "type": "string", + "minLength": 1 + }, + "footer": { + "type": "string", + "minLength": 0 + }, + "id": { + "type": "string", + "minLength": 1 + }, + "token": { + "type": "string", + "minLength": 1 + }, + "action": { + "type": "string", + "enum": [ + "navigate", + "data_exchange" + ] + }, + "screen": { + "type": "string", + "minLength": 0 + }, + "dataJson": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "body", + "buttonText", + "id", + "token", + "action" + ], + "oneOf": [ + { + "properties": { + "action": { + "enum": [ + "navigate" + ] + } + }, + "required": [ + "screen" + ] + }, + { + "properties": { + "action": { + "enum": [ + "data_exchange" + ] + } + }, + "not": { + "anyOf": [ + { + "required": [ + "screen" + ] + }, + { + "required": [ + "dataJson" + ] + } + ] + } + } + ] + } + }, + "required": [ + "flow" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "callPermissionRequest": { + "type": "object", + "properties": { + "body": { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "description": "Why you want to call, shown above WhatsApp's allow and decline buttons. 1 to 1,024 Unicode characters." + } + }, + "required": [ + "body" + ], + "additionalProperties": false, + "description": "Ask the person for permission to call them. Cloud API numbers only; WhatsApp limits how often you can ask." + } + }, + "required": [ + "callPermissionRequest" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "template": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "language": { + "type": "string", + "minLength": 2 + }, + "components": { + "type": "array", + "items": {}, + "maxItems": 1000 + } + }, + "required": [ + "name", + "language" + ], + "additionalProperties": false + } + }, + "required": [ + "template" + ], + "additionalProperties": false + } + ] + }, + "ImageMediaDescriptor": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "pattern": "^https:\\/\\/[!-~]+$", + "description": "Media URL returned by the upload." + }, + "directPath": { + "type": "string", + "maxLength": 2048, + "pattern": "^\\/(?!\\/)[!-~]+$", + "description": "Direct path returned by the upload." + }, + "mediaKey": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 32-byte media key you encrypted the file with." + }, + "fileSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the plaintext file." + }, + "fileEncSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the uploaded ciphertext and MAC." + }, + "fileLength": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648, + "description": "Plaintext file size in bytes." + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^[ -~]+$", + "description": "MIME type of the plaintext file." + }, + "mediaKeyTimestamp": { + "type": "integer", + "minimum": 1, + "maximum": 1099511627776, + "description": "Unix time in seconds when the media key was created." + }, + "thumbnail": { + "type": "string", + "minLength": 4, + "maxLength": 21848, + "description": "Base64-encoded JPEG preview shown before the media downloads, at most 16 KiB. Linked Devices numbers only." + }, + "width": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Width in pixels. Send with height." + }, + "height": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Height in pixels. Send with width." + } + }, + "required": [ + "url", + "directPath", + "mediaKey", + "fileSha256", + "fileEncSha256", + "fileLength", + "mimeType" + ], + "additionalProperties": false, + "description": "Media you encrypted and uploaded yourself." + }, + "VideoMediaDescriptor": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "pattern": "^https:\\/\\/[!-~]+$", + "description": "Media URL returned by the upload." + }, + "directPath": { + "type": "string", + "maxLength": 2048, + "pattern": "^\\/(?!\\/)[!-~]+$", + "description": "Direct path returned by the upload." + }, + "mediaKey": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 32-byte media key you encrypted the file with." + }, + "fileSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the plaintext file." + }, + "fileEncSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the uploaded ciphertext and MAC." + }, + "fileLength": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648, + "description": "Plaintext file size in bytes." + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^[ -~]+$", + "description": "MIME type of the plaintext file." + }, + "mediaKeyTimestamp": { + "type": "integer", + "minimum": 1, + "maximum": 1099511627776, + "description": "Unix time in seconds when the media key was created." + }, + "thumbnail": { + "type": "string", + "minLength": 4, + "maxLength": 21848, + "description": "Base64-encoded JPEG preview shown before the media downloads, at most 16 KiB. Linked Devices numbers only." + }, + "width": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Width in pixels. Send with height." + }, + "height": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Height in pixels. Send with width." + }, + "seconds": { + "type": "integer", + "minimum": 0, + "maximum": 86400, + "description": "Duration in seconds." + } + }, + "required": [ + "url", + "directPath", + "mediaKey", + "fileSha256", + "fileEncSha256", + "fileLength", + "mimeType" + ], + "additionalProperties": false, + "description": "Media you encrypted and uploaded yourself." + }, + "FileMediaDescriptor": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "pattern": "^https:\\/\\/[!-~]+$", + "description": "Media URL returned by the upload." + }, + "directPath": { + "type": "string", + "maxLength": 2048, + "pattern": "^\\/(?!\\/)[!-~]+$", + "description": "Direct path returned by the upload." + }, + "mediaKey": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 32-byte media key you encrypted the file with." + }, + "fileSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the plaintext file." + }, + "fileEncSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the uploaded ciphertext and MAC." + }, + "fileLength": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648, + "description": "Plaintext file size in bytes." + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^[ -~]+$", + "description": "MIME type of the plaintext file." + }, + "mediaKeyTimestamp": { + "type": "integer", + "minimum": 1, + "maximum": 1099511627776, + "description": "Unix time in seconds when the media key was created." + }, + "thumbnail": { + "type": "string", + "minLength": 4, + "maxLength": 21848, + "description": "Base64-encoded JPEG preview shown before the media downloads, at most 16 KiB. Linked Devices numbers only." + } + }, + "required": [ + "url", + "directPath", + "mediaKey", + "fileSha256", + "fileEncSha256", + "fileLength", + "mimeType" + ], + "additionalProperties": false, + "description": "Media you encrypted and uploaded yourself." + }, + "VoiceMediaDescriptor": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "pattern": "^https:\\/\\/[!-~]+$", + "description": "Media URL returned by the upload." + }, + "directPath": { + "type": "string", + "maxLength": 2048, + "pattern": "^\\/(?!\\/)[!-~]+$", + "description": "Direct path returned by the upload." + }, + "mediaKey": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 32-byte media key you encrypted the file with." + }, + "fileSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the plaintext file." + }, + "fileEncSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the uploaded ciphertext and MAC." + }, + "fileLength": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648, + "description": "Plaintext file size in bytes." + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^[ -~]+$", + "description": "MIME type of the plaintext file." + }, + "mediaKeyTimestamp": { + "type": "integer", + "minimum": 1, + "maximum": 1099511627776, + "description": "Unix time in seconds when the media key was created." + }, + "seconds": { + "type": "integer", + "minimum": 0, + "maximum": 86400, + "description": "Duration in seconds." + }, + "waveform": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 voice-note waveform, 64 to 192 bytes." + } + }, + "required": [ + "url", + "directPath", + "mediaKey", + "fileSha256", + "fileEncSha256", + "fileLength", + "mimeType" + ], + "additionalProperties": false, + "description": "Media you encrypted and uploaded yourself. Upload voice notes with type voice." + }, + "StickerMediaDescriptor": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "pattern": "^https:\\/\\/[!-~]+$", + "description": "Media URL returned by the upload." + }, + "directPath": { + "type": "string", + "maxLength": 2048, + "pattern": "^\\/(?!\\/)[!-~]+$", + "description": "Direct path returned by the upload." + }, + "mediaKey": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 32-byte media key you encrypted the file with." + }, + "fileSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the plaintext file." + }, + "fileEncSha256": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 SHA-256 of the uploaded ciphertext and MAC." + }, + "fileLength": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648, + "description": "Plaintext file size in bytes." + }, + "mimeType": { + "type": "string", + "enum": [ + "image/webp" + ] + }, + "mediaKeyTimestamp": { + "type": "integer", + "minimum": 1, + "maximum": 1099511627776, + "description": "Unix time in seconds when the media key was created." + }, + "width": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Width in pixels. Send with height." + }, + "height": { + "type": "integer", + "minimum": 1, + "maximum": 65536, + "description": "Height in pixels. Send with width." + }, + "isAnimated": { + "type": "boolean" + } + }, + "required": [ + "url", + "directPath", + "mediaKey", + "fileSha256", + "fileEncSha256", + "fileLength", + "mimeType" + ], + "additionalProperties": false + }, + "SendMessageResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/SentMessage" + } + }, + "required": [ + "success", + "data" + ] + }, + "SentMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + }, + "conversation": { + "allOf": [ + { + "$ref": "#/components/schemas/ConversationIdentity" + }, + { + "description": "The message's conversation, including its public ID and known aliases." + } + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Upstream acceptance status. Delivery and read receipts arrive separately." + }, + "type": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/MessageContent" + }, + "mediaId": { + "type": "string" + }, + "hdMessageId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "ID of the HD half of an image pair, present when `quality: \"hd\"` sent one. `id` is the standard-quality message.", + "example": "739182640518203" + } + }, + "required": [ + "id", + "whatsapp_ids", + "conversation", + "timestamp", + "status", + "type", + "content" + ] + }, + "WhatsAppMessageIds": { + "anyOf": [ + { + "type": "object", + "properties": { + "linked_devices": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "official_api": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "linked_devices" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "linked_devices": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "official_api": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "official_api" + ], + "additionalProperties": false + } + ], + "description": "Exact message references observed through Linked Devices, the Official API, or both. Use the Polymorfa message id for actions and replies. Unknown provider references are omitted." + }, + "ConversationIdentity": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "phoneNumber": { + "type": "string", + "pattern": "^\\+[1-9]\\d{1,14}$", + "description": "Phone number reported by WhatsApp, when available. It can differ from the number supplied for lookup. Use id for later actions and replies." + }, + "bsuid": { + "type": "string", + "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" + }, + "username": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "AsyncAcceptedResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "requestId" + ] + } + }, + "required": [ + "success", + "data" + ] + }, + "PublicError": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "billing_error", + "conflict_error", + "rate_limit_error", + "api_error", + "upstream_error" + ] + }, + "code": { + "type": "string", + "enum": [ + "zero_knowledge_session", + "invalid_parameter", + "invalid_credential", + "entitlement_required", + "gate_limit_reached", + "billing_credit_required", + "addon_required", + "safe_mode_not_entitled", + "warmup_plan_not_entitled", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "hms_not_enabled", + "media_fetch_failed", + "resource_gone", + "feature_unavailable", + "stream_cursor_invalid", + "stream_cursor_expired", + "stream_connection_limit_reached", + "state_conflict", + "team_deletion_scheduled", + "hybrid_choice_required", + "hybrid_group_linked_devices_unavailable", + "idempotency_conflict", + "idempotency_in_progress", + "idempotency_completed", + "idempotency_outcome_unknown", + "call_claimed", + "call_not_ringing", + "connection_limit", + "unsupported_for_connection", + "call_permission_required", + "invalid_sip_trunk", + "calls_disabled", + "call_recipient_opted_out", + "call_destination_blocked", + "call_permission_request_limited", + "call_permission_granted", + "call_opt_out_limit", + "number_restricted", + "sip_trunk_in_use", + "sip_trunk_revision_conflict", + "sip_trunk_limit", + "sip_unavailable", + "voice_not_enabled", + "provider_credential_invalid", + "provider_unavailable", + "asset_not_ready", + "voice_asset_in_use", + "voice_asset_revision_conflict", + "voice_unavailable", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown", + "parent_bsuid_not_supported", + "recipient_not_on_whatsapp", + "conversation_window_closed", + "template_not_approved", + "media_too_large", + "whatsapp_rate_limited", + "new_chat_limit_reached", + "marketing_disabled_on_cloud_api", + "whatsapp_account_restricted", + "whatsapp_authorization_required", + "whatsapp_payment_method_required", + "whatsapp_number_not_registered", + "bansafe_suspended", + "bansafe_org_suspended", + "bansafe_cold_blocked", + "bansafe_cold_held", + "bansafe_throttled", + "bansafe_daily_allowance_reached", + "bansafe_accounting_unavailable", + "bansafe_send_outcome_unknown", + "campaigns_not_entitled", + "campaign_throughput_capped", + "campaign_approval_required", + "campaign_approval_stale", + "campaign_variables_missing" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + }, + "request_id": { + "type": "string", + "description": "Matches the X-Request-Id response header. Include it when you contact support." + }, + "request_log_url": { + "type": "string", + "format": "uri", + "description": "Console page that shows this request. Returned only to organization and project credentials whose request was logged." + } + }, + "required": [ + "type", + "code", + "message", + "param", + "request_id" + ] + }, + "data": { + "type": "string", + "nullable": true, + "enum": [ + null + ], + "description": "Always null for a failed request." + }, + "docs": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "error", + "data", + "docs" + ] + }, + "ReactRequest": { + "type": "object", + "properties": { + "conversation": { + "$ref": "#/components/schemas/ConversationReference" + }, + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "reaction": { + "type": "string" + } + }, + "required": [ + "conversation", + "id", + "reaction" + ], + "additionalProperties": false + }, + "SendReactionResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/MessageReceipt" + } + }, + "required": [ + "success", + "data" + ] + }, + "MessageReceipt": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + }, + "conversation": { + "allOf": [ + { + "$ref": "#/components/schemas/ConversationIdentity" + }, + { + "description": "The message's conversation, including its public ID and known aliases." + } + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Upstream acceptance status. Delivery and read receipts arrive separately." + } + }, + "required": [ + "id", + "whatsapp_ids", + "conversation", + "timestamp", + "status" + ] + }, + "ListProjectTemplatesResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageTemplate" + } + } + }, + "required": [ + "success", + "data" + ] + }, + "MessageTemplate": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "category": { + "type": "string" + }, + "language": { + "type": "string" + }, + "status": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "definition": {}, + "sampleValues": {}, + "cloudLinks": { + "type": "array", + "items": {} + }, + "createdAt": { + "type": "number" + }, + "updatedAt": { + "type": "number" + } + }, + "required": [ + "id", + "name", + "category", + "language", + "status", + "kind", + "cloudLinks", + "createdAt", + "updatedAt" + ] + }, + "GetProjectTemplateResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/MessageTemplate" + } + }, + "required": [ + "success", + "data" + ] + }, + "MessageReceivedPayload": { + "oneOf": [ + { + "$ref": "#/components/schemas/MessagePayload" + }, + { + "$ref": "#/components/schemas/CloudMessagePayload" + } + ] + }, + "MessagePayload": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + }, + "conversation": { + "$ref": "#/components/schemas/ConversationRef" + }, + "fromMe": { + "type": "boolean" + }, + "timestamp": { + "type": "number" + }, + "pushName": { + "type": "string" + }, + "isGroup": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "text", + "image", + "video", + "audio", + "document", + "location", + "contact", + "phone_number_shared", + "native_flow_response", + "button_reply", + "list_reply", + "poll", + "sticker", + "reaction", + "revoke", + "edited", + "unknown" + ] + }, + "text": { + "type": "string" + }, + "caption": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "ptt": { + "type": "boolean" + }, + "filename": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "displayName": { + "type": "string" + }, + "title": { + "type": "string" + }, + "reaction": { + "type": "string" + }, + "reactionTo": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "revokedId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "nativeFlowResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "paramsJson": { + "type": "string", + "minLength": 1, + "maxLength": 262144 + }, + "version": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "name", + "paramsJson" + ] + }, + "replyChoice": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "button", + "list" + ], + "description": "Reply control the contact used: a reply or quick-reply button, or a list row." + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "ID you assigned to the selected button or list row. It is never the visible label, except for a template quick-reply button sent without a payload, where WhatsApp returns the button text." + } + }, + "required": [ + "kind", + "id" + ], + "description": "Present when the contact selects a reply button or list row." + }, + "parentMessageId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "WhatsApp ID of the message this one quotes, when WhatsApp supplies one. It is not necessarily a message you sent." + }, + "media": { + "type": "string" + }, + "mediaUrl": { + "type": "string" + }, + "edited": { + "type": "boolean" + }, + "pollOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PollOption" + } + }, + "unavailable": { + "type": "boolean" + }, + "unavailableReason": { + "type": "string" + }, + "width": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Image, video or sticker width in pixels, as declared by the sender." + }, + "height": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Image, video or sticker height in pixels, as declared by the sender." + }, + "seconds": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Audio or video duration in seconds." + }, + "waveform": { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "maxItems": 64, + "description": "Voice-note waveform: up to 64 samples from 0 to 1." + }, + "pageCount": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Document page count, as declared by the sender." + }, + "thumbnail": { + "$ref": "#/components/schemas/MediaThumbnail" + }, + "linkPreview": { + "$ref": "#/components/schemas/LinkPreview" + }, + "association": { + "$ref": "#/components/schemas/MessageAssociation" + }, + "quality": { + "type": "string", + "enum": [ + "standard", + "hd" + ], + "description": "`hd` on the HD half of a pair; `standard` on a standard message that announced an HD half." + } + }, + "required": [ + "id", + "whatsapp_ids", + "conversation", + "fromMe", + "timestamp", + "pushName", + "isGroup", + "type" + ] + }, + "ConversationRef": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "phoneNumber": { + "type": "string" + }, + "bsuid": { + "type": "string" + }, + "username": { + "type": "string" + }, + "parentBsuid": { + "type": "string", + "description": "Parent business-scoped user ID (`CC.ENT.…`), present only when WhatsApp reports one for this person. It identifies the person across the business portfolios enrolled in one parent account. It cannot be used to send messages or block people; use `id` or `bsuid`.", + "example": "US.ENT.11815799212886844830" + }, + "sender": { + "$ref": "#/components/schemas/IdentityRef" + } + }, + "required": [ + "id" + ] + }, + "IdentityRef": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "phoneNumber": { + "type": "string" + }, + "bsuid": { + "type": "string" + }, + "username": { + "type": "string" + }, + "parentBsuid": { + "type": "string", + "description": "Parent business-scoped user ID (`CC.ENT.…`), present only when WhatsApp reports one for this person. It identifies the person across the business portfolios enrolled in one parent account. It cannot be used to send messages or block people; use `id` or `bsuid`.", + "example": "US.ENT.11815799212886844830" + } + }, + "required": [ + "id" + ] + }, + "PollOption": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "hash": { + "type": "string" + } + }, + "required": [ + "name", + "hash" + ] + }, + "MediaThumbnail": { + "type": "object", + "properties": { + "contentType": { + "type": "string", + "enum": [ + "image/jpeg" + ] + }, + "data": { + "type": "string", + "description": "Base64-encoded JPEG bytes, at most 16 KiB decoded." + }, + "width": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "height": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + } + }, + "required": [ + "contentType", + "data" + ], + "description": "Low-resolution JPEG preview embedded in the message. Present only when the sender embedded one of at most 16 KiB; previews WhatsApp stores separately are not fetched." + }, + "LinkPreview": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The URL the preview describes, as matched in the message text." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "previewType": { + "type": "string", + "description": "WhatsApp's preview kind in lower case, such as `none`, `video` or `image`." + }, + "thumbnail": { + "$ref": "#/components/schemas/MediaThumbnail" + } + }, + "required": [ + "url" + ], + "description": "Preview the sender attached to a text message containing a URL." + }, + "MessageAssociation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "hd_image", + "hd_video" + ] + }, + "parentMessageId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "`id` of the standard-quality message this HD message belongs to. It can arrive before that message.", + "example": "739182640518203" + } + }, + "required": [ + "type", + "parentMessageId" + ], + "description": "Present on the HD half of an HD media pair. Show it in place of the standard message it names." + }, + "CloudMessagePayload": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + }, + "conversation": { + "$ref": "#/components/schemas/ConversationRef" + }, + "timestamp": { + "type": "string" + }, + "type": { + "type": "string" + }, + "senderName": { + "type": "string" + }, + "nativeFlowResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "paramsJson": { + "type": "string", + "minLength": 1, + "maxLength": 262144 + }, + "version": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "name", + "paramsJson" + ] + }, + "replyChoice": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "button", + "list" + ], + "description": "Reply control the contact used: a reply or quick-reply button, or a list row." + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "ID you assigned to the selected button or list row. It is never the visible label, except for a template quick-reply button sent without a payload, where WhatsApp returns the button text." + } + }, + "required": [ + "kind", + "id" + ], + "description": "Present when the contact selects a reply button or list row." + }, + "parentMessageId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "WhatsApp ID of the message this one quotes, when WhatsApp supplies one. It is not necessarily a message you sent." + }, + "interactive": { + "type": "object", + "additionalProperties": {} + }, + "order": { + "$ref": "#/components/schemas/CloudOrder" + }, + "referral": { + "type": "object", + "properties": { + "source_type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "ctwa_clid": { + "type": "string" + } + }, + "description": "Meta-supplied Click-to-WhatsApp entry point. Does not establish a conversion or payment." + } + }, + "required": [ + "id", + "whatsapp_ids", + "conversation", + "timestamp", + "type" + ] + }, + "CloudOrder": { + "type": "object", + "properties": { + "catalogId": { + "type": "string", + "pattern": "^[0-9]{1,32}$", + "description": "Meta catalog ID the cart was built from." + }, + "text": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "description": "Text the contact sent with the order." + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CloudOrderItem" + }, + "minItems": 1, + "maxItems": 1000 + } + }, + "required": [ + "catalogId", + "items" + ], + "description": "Present on `type: \"order\"` messages when every cart line is readable. There is no order total: multiply `quantity` by `itemPrice` per item if you need one. Prices and availability can differ from your catalog; confirm them before you fulfil the order." + }, + "CloudOrderItem": { + "type": "object", + "properties": { + "productRetailerId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "Your retailer ID (SKU), at most 256 UTF-8 bytes with no control characters." + }, + "quantity": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Units ordered." + }, + "itemPrice": { + "type": "number", + "minimum": 0, + "description": "Unit price as Meta reported it. Polymorfa does not convert or recompute it." + }, + "currency": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "description": "ISO 4217 currency code Meta reported for the item." + } + }, + "required": [ + "productRetailerId", + "quantity", + "itemPrice", + "currency" + ] + }, + "MessageSentPayload": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + }, + "conversation": { + "$ref": "#/components/schemas/ConversationRef" + }, + "type": { + "type": "string" + }, + "timestamp": { + "type": "number" + }, + "width": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Image, video or sticker width in pixels, as declared by the sender." + }, + "height": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Image, video or sticker height in pixels, as declared by the sender." + }, + "seconds": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Audio or video duration in seconds." + }, + "waveform": { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "maxItems": 64, + "description": "Voice-note waveform: up to 64 samples from 0 to 1." + }, + "pageCount": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Document page count, as declared by the sender." + }, + "thumbnail": { + "$ref": "#/components/schemas/MediaThumbnail" + }, + "linkPreview": { + "$ref": "#/components/schemas/LinkPreview" + }, + "association": { + "$ref": "#/components/schemas/MessageAssociation" + }, + "quality": { + "type": "string", + "enum": [ + "standard", + "hd" + ], + "description": "`hd` on the HD half of a pair; `standard` on a standard message that announced an HD half." + } + }, + "required": [ + "id", + "whatsapp_ids", + "conversation", + "type", + "timestamp" + ] + }, + "MessageAckPayload": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "whatsapp_ids": { + "$ref": "#/components/schemas/WhatsAppMessageIds" + }, + "whatsapp_id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Temporary singular provider reference for older consumers. Use whatsapp_ids for transport-specific references.", + "deprecated": true + } + }, + "required": [ + "id", + "whatsapp_ids" + ] + } + }, + "conversation": { + "$ref": "#/components/schemas/ConversationRef" + }, + "from": { + "$ref": "#/components/schemas/IdentityRef" + }, + "sender": { + "$ref": "#/components/schemas/IdentityRef" + }, + "type": { + "type": "string", + "description": "Ack type: error, delivered, read, played" + }, + "timestamp": { + "type": "number" + }, + "pricing": { + "$ref": "#/components/schemas/MetaPricingReport" + } + }, + "required": [ + "messages", + "conversation", + "type", + "timestamp" + ] + }, + "MetaPricingReport": { + "type": "object", + "properties": { + "billable": { + "type": "boolean", + "description": "Whether WhatsApp reported the message as billable. WhatsApp is deprecating this field; use `type`." + }, + "pricing_model": { + "type": "string", + "description": "WhatsApp pricing model, for example `PMP` (per-message pricing).", + "example": "PMP" + }, + "category": { + "type": "string", + "description": "WhatsApp pricing category, for example `marketing`, `utility`, `authentication` or `service`.", + "example": "utility" + }, + "type": { + "type": "string", + "description": "WhatsApp pricing type, for example `regular`, `free_customer_service` or `free_entry_point`.", + "example": "free_customer_service" + } + }, + "description": "Official API only. The pricing classification WhatsApp reported for this message, copied as reported. Meta bills your WhatsApp Business Account directly; this is not a Polymorfa charge and contains no price." + }, + "MessageFailedPayload": { + "type": "object", + "properties": { + "to": { + "allOf": [ + { + "$ref": "#/components/schemas/IdentityRef" + }, + { + "description": "Recipient chat the send was attempted to" + } + ] + }, + "type": { + "type": "string", + "description": "Type of the attempted message (text, image, ...)", + "example": "text" + }, + "error": { + "type": "string", + "enum": [ + "invalid_recipient", + "session_not_connected", + "ack_timeout", + "send_failed", + "blocked_by_safety" + ], + "description": "Sanitized failure class", + "example": "send_failed" + }, + "code": { + "type": "string", + "description": "Stable machine-readable code from the API error envelope, present for classified safety refusals and an unknown send outcome", + "example": "bansafe_cold_blocked" + }, + "retryAfter": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Whole seconds to wait before retrying a paced send, exhausted allowance, or temporary allowance-accounting pause", + "example": 5 + }, + "timestamp": { + "type": "integer", + "minimum": 0, + "description": "Unix timestamp in seconds when the failure occurred", + "example": 1757161200 + } + }, + "required": [ + "to", + "type", + "error", + "timestamp" + ] + }, + "SessionStatusPayload": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeSessionStatusPayload" + }, + { + "$ref": "#/components/schemas/CloudAccountStatusPayload" + } + ] + }, + "RuntimeSessionStatusPayload": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Session status: CONNECTING, CONNECTED, DISCONNECTED", + "example": "DISCONNECTED" + }, + "statusReason": { + "type": "string", + "description": "Reason for the status: SCAN_QR, AUTO_RECONNECT, FAILED, MANUAL_STOP, QR_TIMEOUT, LOGGED_OUT, TEMPORARY_BAN, STREAM_ERROR", + "example": "TEMPORARY_BAN" + }, + "banCode": { + "type": "integer", + "minimum": 0, + "description": "WhatsApp temporary-ban code (present only when statusReason is TEMPORARY_BAN).", + "example": 101 + }, + "banReason": { + "type": "string", + "description": "Human-readable label for the ban code (present only when statusReason is TEMPORARY_BAN).", + "example": "Suspected spam activity" + }, + "banExpiresAt": { + "type": "integer", + "description": "Unix timestamp in seconds when the temporary ban lifts, when WhatsApp provides a duration.", + "format": "int64", + "example": 1789000000 + }, + "detail": { + "type": "string", + "description": "Extra context for FAILED and STREAM_ERROR statuses (for example the connect-failure reason)." + } + }, + "required": [ + "status" + ] + }, + "CloudAccountStatusPayload": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "meta" + ], + "description": "Always `meta`: a WhatsApp Business Account notification, not a connection-state change" + }, + "kind": { + "type": "string", + "enum": [ + "account_alerts", + "account_update", + "phone_number_name_update" + ], + "description": "WhatsApp notification field that produced the event" + }, + "wabaId": { + "type": "string", + "description": "WhatsApp Business Account id the notification belongs to", + "example": "102290129340398" + }, + "value": { + "type": "object", + "additionalProperties": {}, + "description": "The notification's `value` object exactly as WhatsApp sent it; its fields vary by notification" + } + }, + "required": [ + "source", + "kind", + "value" + ] + }, + "SessionConnectedPayload": { + "type": "object", + "properties": { + "phoneNumber": { + "type": "string" + }, + "id": { + "type": "string", + "description": "Polymorfa ID of the connected account." + }, + "pushName": { + "type": "string" + }, + "phonePlatform": { + "type": "string", + "enum": [ + "android", + "ios", + "meta_cloud", + "unknown" + ], + "description": "Where the account's primary WhatsApp client runs.", + "x-enumDescriptions": { + "android": "WhatsApp or WhatsApp Business on an Android phone.", + "ios": "WhatsApp or WhatsApp Business on an iPhone.", + "meta_cloud": "Meta hosts the number through Cloud API.", + "unknown": "The phone has not reported a recognized platform." + } + }, + "accountType": { + "type": "string", + "enum": [ + "whatsapp_app", + "business_app", + "meta_cloud", + "meta_coexistence" + ], + "description": "How the account uses WhatsApp.", + "x-enumDescriptions": { + "whatsapp_app": "WhatsApp app, connected as a linked device.", + "business_app": "WhatsApp Business app, connected as a linked device.", + "meta_cloud": "Meta Cloud API.", + "meta_coexistence": "Meta Cloud API, with the WhatsApp Business app still active on the phone." + } + }, + "businessName": { + "type": "string" + } + }, + "required": [ + "phoneNumber", + "pushName", + "phonePlatform", + "accountType" + ] + }, + "SessionLoggedOutPayload": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "enum": [ + "banned", + "device_removed", + "unknown" + ], + "description": "Why the session was logged out: `banned` when WhatsApp banned the account, `device_removed` when the linked device was removed from the phone or from another device, `unknown` otherwise (including a locked account).", + "example": "device_removed" + }, + "code": { + "type": "integer", + "description": "WhatsApp's logout code: 401 (removed), 403 (locked) or 406 (banned); 0 when WhatsApp gave none.", + "example": 401 + } + }, + "required": [ + "reason", + "code" + ] + }, + "CallEndedPayload": { + "type": "object", + "properties": { + "from": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" + }, + "phoneNumber": { + "type": "string" + }, + "bsuid": { + "type": "string" + }, + "username": { + "type": "string" + }, + "parentBsuid": { + "type": "string", + "description": "Parent business-scoped user ID (`CC.ENT.…`), present only when WhatsApp reports one for this person. It identifies the person across the business portfolios enrolled in one parent account. It cannot be used to send messages or block people; use `id` or `bsuid`.", + "example": "US.ENT.11815799212886844830" + } + }, + "required": [ + "id" + ] + }, + "callId": { + "type": "string" + }, + "durationSeconds": { + "type": "integer", + "description": "Total connected duration in seconds (0 if never answered).", + "example": 42 + }, + "reason": { + "type": "string", + "description": "Termination reason: user_hangup | timeout | lost_connection | rejected | capacity | pod_lost | connection_failed | upstream_failed | call_restricted (WhatsApp refused the call because the number is restricted to calling people it has already chatted with).", + "example": "user_hangup" + }, + "direction": { + "type": "string", + "enum": [ + "inbound", + "outbound" + ], + "description": "Call direction.", + "example": "inbound" + }, + "hadVideo": { + "type": "boolean", + "description": "Whether the call carried video.", + "example": false + }, + "sessionConnection": { + "type": "string", + "enum": [ + "linked_device", + "cloud_api" + ], + "description": "Connection of the session that carries the call: a linked device or the WhatsApp Business Cloud API.", + "example": "linked_device" + } + }, + "required": [ + "from", + "callId", + "durationSeconds", + "reason", + "direction", + "hadVideo" + ] + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Polymorfa team key (pmfa_ prefix), project token (pmfa_pt_ prefix) where supported, or short-lived client token (pmfa_ct_ prefix). Team keys and project tokens may be personal or service-account owned and enforce selected action scopes. Team keys cover the team and its projects; project tokens stay inside one project. Client tokens stay inside their live project and session and enforce their client rules and immutable mint-time action ceiling. Treat all credentials as opaque strings. Never parse them or assume a fixed length; store at least 512 characters. Server keys use issuance-record lookup; short-lived client tokens carry a signed permission ceiling." + } + }, + "headers": { + "X-RateLimit-Limit": { + "description": "Team request quota per rolling minute, when the per-team request limiter applies.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the per-team quota; zero when this limiter rejects the request.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp in seconds when the oldest counted request leaves the rolling window, when the per-team request limiter applies. Follow Retry-After on a rejected request.", + "schema": { + "type": "integer", + "minimum": 0 + } + } + } + } +} diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json new file mode 100644 index 0000000..221f0de --- /dev/null +++ b/contracts/openapi.platform.json @@ -0,0 +1,1330 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Polymorfa automation platform", + "version": "2026-09-22" + }, + "servers": [ + { + "url": "https://api.polymorfa.com" + } + ], + "paths": { + "/platform/sessions": { + "get": { + "operationId": "listPlatformSessions", + "tags": [ + "sessions" + ], + "summary": "List sessions", + "description": "Lists sessions in the authenticated team or selected project, including disconnected sessions and their latest status reason. A Test key returns only test numbers in its project, or an empty list when there are none. Other project tokens return all sessions in their own project.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", + "x-merged-endpoints": [ + "listSessionsByOrganization" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformSession" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/platform/projects/{projectId}/webhooks": { + "get": { + "operationId": "listProjectWebhooks", + "tags": [ + "webhooks" + ], + "summary": "List project webhooks", + "description": "Lists webhooks owned by the exact project in the route.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:read", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "eventType", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + { + "name": "enabled", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectWebhook" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "post": { + "operationId": "createProjectWebhook", + "tags": [ + "webhooks" + ], + "summary": "Create a project webhook", + "description": "Creates a project-owned webhook and returns its generated signing secret once.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:manage", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectWebhookInput" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWebhookCreationReceipt" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/platform/projects/{projectId}/webhooks/{webhookId}": { + "get": { + "operationId": "getProjectWebhook", + "tags": [ + "webhooks" + ], + "summary": "Get a project webhook", + "description": "Returns one webhook owned by the exact project without secret values.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:read", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWebhook" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "delete": { + "operationId": "deleteProjectWebhook", + "tags": [ + "webhooks" + ], + "summary": "Delete a project webhook", + "description": "Deletes one webhook owned by the exact project.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:manage", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWebhookDeletionReceipt" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + } + }, + "components": { + "schemas": { + "PlatformSession": { + "type": "object", + "additionalProperties": false, + "properties": { + "_id": { + "type": "string", + "format": "uuid" + }, + "_creationTime": { + "type": "number", + "description": "Creation time as Unix epoch milliseconds." + }, + "projectId": { + "$ref": "#/components/schemas/ProjectId" + }, + "sessionId": { + "$ref": "#/components/schemas/SessionIdentifier" + }, + "name": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "platform": { + "type": "string", + "nullable": true + }, + "isBusiness": { + "type": "boolean" + }, + "testMode": { + "type": "boolean" + }, + "tierOverride": { + "type": "string", + "enum": [ + "free", + "standard", + "pro", + "scale", + null + ], + "nullable": true + }, + "status": { + "type": "string", + "description": "Latest runtime connection status." + }, + "statusReason": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the latest connection status, when available." + }, + "messageCount": { + "type": "integer", + "minimum": 0 + }, + "lastActiveAt": { + "type": "number", + "nullable": true, + "description": "Last activity time as Unix epoch milliseconds." + }, + "paidUntil": { + "type": "number", + "nullable": true, + "description": "End of the charged rolling 24-hour window as Unix epoch milliseconds. Null when no charged window exists; expired windows retain their expiry." + }, + "customer": { + "type": "object", + "nullable": true, + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "nullable": true + }, + "isDefault": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "isDefault" + ] + }, + "claim": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "type": "string", + "enum": [ + "unclaimed" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "type": "string", + "enum": [ + "claimed-elsewhere" + ] + }, + "displayName": { + "type": "string" + }, + "avatarUrl": { + "type": "string", + "nullable": true + }, + "avatarSeed": { + "type": "string" + }, + "claimedAt": { + "type": "number" + } + }, + "required": [ + "kind", + "displayName", + "avatarUrl", + "avatarSeed", + "claimedAt" + ] + } + ] + } + }, + "required": [ + "_id", + "_creationTime", + "projectId", + "sessionId", + "name", + "phone", + "platform", + "isBusiness", + "testMode", + "tierOverride", + "status", + "statusReason", + "messageCount", + "lastActiveAt", + "paidUntil", + "customer", + "claim" + ] + }, + "ProjectId": { + "type": "string", + "format": "uuid", + "description": "Project UUID. Optional where an operation can infer or search team scope; required where the operation creates a project-owned resource." + }, + "SessionIdentifier": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Database session UUID or stable session slug." + }, + "ErrorEnvelope": { + "type": "object", + "required": [ + "error", + "data", + "docs" + ], + "properties": { + "error": { + "type": "object", + "required": [ + "type", + "code", + "message", + "param", + "request_id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "billing_error", + "conflict_error", + "rate_limit_error", + "api_error", + "upstream_error" + ] + }, + "code": { + "type": "string", + "description": "Stable failure code. Use this value for programmatic handling." + }, + "message": { + "type": "string", + "description": "Safe explanation of the failure." + }, + "param": { + "type": "string", + "nullable": true, + "description": "Invalid request field, or null when no field applies." + }, + "request_id": { + "type": "string", + "description": "Matches the X-Request-Id response header. Include it when you contact support." + }, + "request_log_url": { + "type": "string", + "format": "uri", + "description": "Console page that shows this request. Returned only to organization and project credentials whose request was logged." + } + } + }, + "data": { + "type": "string", + "nullable": true, + "enum": [ + null + ], + "description": "Always null for a failed request." + }, + "docs": { + "type": "string", + "format": "uri", + "description": "Documentation link to this failure code." + } + } + }, + "ProjectWebhook": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "owner": { + "type": "string", + "enum": [ + "project" + ] + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "enabled": { + "type": "boolean" + }, + "format": { + "type": "string", + "enum": [ + "native", + "meta" + ] + }, + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicy" + }, + "phoneOfflineStartDay": { + "type": "integer", + "minimum": 7, + "maximum": 13, + "description": "First day without the phone on which this endpoint receives session.phone_offline." + }, + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderMetadata" + } + }, + "secret": { + "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "organizationId", + "owner", + "projectId", + "url", + "eventTypes", + "enabled", + "format", + "retryPolicy", + "phoneOfflineStartDay", + "headers", + "secret", + "createdAt", + "updatedAt" + ] + }, + "WebhookRetryPolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "maximumAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 10 + }, + "backoff": { + "type": "string", + "enum": [ + "constant", + "linear", + "exponential" + ] + }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 1, + "maximum": 3600 + } + }, + "required": [ + "maximumAttempts", + "backoff", + "initialDelaySeconds" + ] + }, + "WebhookHeaderMetadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "required": [ + "name" + ] + }, + "WebhookSigningSecretMetadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "type": "integer", + "minimum": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "previousValidUntil": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "required": [ + "version", + "createdAt", + "previousValidUntil" + ] + }, + "CursorPageMetadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "nextCursor": { + "type": "string", + "nullable": true + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "nextCursor", + "hasMore" + ] + }, + "CreateProjectWebhookInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2048 + }, + "eventTypes": { + "description": "Names reserved for internal use cannot be newly subscribed. Existing stored entries may be preserved during updates but never match deliveries.", + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "enabled": { + "type": "boolean", + "default": true + }, + "format": { + "type": "string", + "enum": [ + "native", + "meta" + ], + "default": "native" + }, + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicyInput" + }, + "phoneOfflineStartDay": { + "type": "integer", + "minimum": 7, + "maximum": 13, + "default": 10, + "description": "First day without the phone on which this endpoint receives session.phone_offline. WhatsApp signs the linked device out on day 14. On PATCH, omission preserves the stored value. To stop the warning, remove session.phone_offline from eventTypes." + }, + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderInput" + } + } + }, + "required": [ + "url", + "eventTypes" + ] + }, + "WebhookRetryPolicyInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "maximumAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 10 + }, + "backoff": { + "type": "string", + "enum": [ + "constant", + "linear", + "exponential" + ] + }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 1, + "maximum": 3600 + } + }, + "required": [ + "maximumAttempts", + "backoff", + "initialDelaySeconds" + ] + }, + "WebhookHeaderInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$" + }, + "value": { + "type": "string", + "maxLength": 4096, + "pattern": "^(?![\\s\\S]*[\\u0000-\\u0008\\u000A-\\u001F\\u007F])[\\s\\S]*$", + "x-polymorfa-max-utf8-bytes": 4096, + "writeOnly": true, + "description": "Encrypted write-only HTTP field value. Limited to 4096 UTF-8 bytes. Line breaks, NUL, and other control characters except horizontal tab are rejected." + } + }, + "required": [ + "name", + "value" + ] + }, + "ProjectWebhookCreationReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "webhook": { + "$ref": "#/components/schemas/ProjectWebhook" + }, + "operationId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + }, + "secret": { + "type": "string", + "nullable": true, + "writeOnly": true + }, + "secretAvailable": { + "type": "boolean" + } + }, + "required": [ + "webhook", + "operationId", + "idempotency", + "secret", + "secretAvailable" + ] + }, + "IdempotencyReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "key": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "replayed": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "key", + "replayed", + "createdAt", + "expiresAt" + ] + }, + "ProjectWebhookDeletionReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "webhookId": { + "type": "string", + "format": "uuid" + }, + "deleted": { + "type": "boolean", + "enum": [ + true + ] + }, + "operationId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + } + }, + "required": [ + "webhookId", + "deleted", + "operationId", + "idempotency" + ] + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "http", + "scheme": "bearer", + "description": "Send your team API key or project token as `Authorization: Bearer `. Treat credentials as opaque strings. Never parse them or assume a fixed length; store at least 512 characters. Permissions and ownership come from the live issuance record. Project tokens can access only their own project." + } + }, + "responses": { + "BadRequest": { + "description": "A path, query, or request-body value did not pass validation.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "Unauthorized": { + "description": "Missing/invalid credential for this audience.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "Forbidden": { + "description": "Authenticated but lacks the required scope/role/audience.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "NotFound": { + "description": "The requested resource was not found inside the authenticated team or project scope.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "Conflict": { + "description": "The request conflicts with the resource's current state.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "X-Trace-Id": { + "schema": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "description": "W3C trace ID of this request. Your `traceparent` trace ID when you sent a valid one." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } +} diff --git a/contracts/sdk-baseline.json b/contracts/sdk-baseline.json new file mode 100644 index 0000000..bf34010 --- /dev/null +++ b/contracts/sdk-baseline.json @@ -0,0 +1,61 @@ +{ + "source": "b3bd05843342275f25f304bfe1fc67ca872740ba", + "note": "Fingerprints of reviewed public SDK fragments. The SDK audience differs from the public API projection; no extra operation is imported. Changes require reconciliation, not automatic repinning.", + "fragments": { + "messaging": { + "POST /messaging/{session}/messages/send": "93a1803cdc95b7fe7d8d74d829a4e80587a4c0c9fc5ac5b2c33ba16c8de802d2", + "#/components/schemas/SendMessageRequest": "8386e01b57b58f9c9ab5121eee080bad3064f9c14df978e8d020a3057ff796e3", + "#/components/schemas/ConversationReference": "1f4775033765255d64b33210b8b8f2d46657ecb0ae79aa6de890074b79fecd1a", + "#/components/schemas/QuotedMessage": "e62e128f03711c6436ccc1ed81a3b3455c2320a0fa49913510ea005f52007b47", + "#/components/schemas/MessageContent": "60672a06e516d8616144dca4f0402cc51fe8d4daa51fc96f952c01a4c2bdecb0", + "#/components/schemas/OrderDetailsContent": "68ea6689f4f688e78cc0225339f6790f0015d3e0545b24362839dd3e75f8cca7", + "#/components/schemas/PaymentOrderAmount": "fe7303e8acd3f51ba5c93cd55adde3e8b20e67b7f5806d3a3bd49e55545346b9", + "#/components/schemas/OrderStatusContent": "97d62fab1e4cf5d1ef32a368ace21e99e2781db74871b1b0116cdb8131ac8760", + "#/components/schemas/SendMessageResponse": "92f27ea117567391bfe669ec3e6e5181b15eb9a9591e1aebd1850ed5361bbd48", + "#/components/schemas/SentMessage": "39e68ee360e477804ccd31463e13408bafbfd20c0a1507e351d6e053ebc6994f", + "#/components/schemas/WhatsAppMessageIds": "da7eed5d59e28c5a8fcf17d9ef94ff47ea5c26fb1f949996a6d959b91d21bedf", + "#/components/schemas/ConversationIdentity": "c35833feef8f250a734714f2c5dc209b4d8ab3ac5a65ca4b2212ca2371f620e3", + "#/components/headers/X-RateLimit-Limit": "b2965eeaed5d8b40ddc39a5ffe5cb6e042e275517f264bb7ed9ba2ba4e18fab1", + "#/components/headers/X-RateLimit-Remaining": "0610aa688cc7c86ff568195eaa5f93a92df64d62dc4e872007ed39b56682952c", + "#/components/headers/X-RateLimit-Reset": "2bbf04658a8168fde104816fbd462bdf6bfcc9203bfacd253b17f845c2cd4774", + "#/components/schemas/AsyncAcceptedResponse": "49e01a5a3f22c3fc2fa173292e14fe1bc413fbf1e4292239173eb40738bb0c8b", + "#/components/schemas/PublicError": "f70f822587b737f02c8c00d3a78fdb66ed2e7ac186208055a8db32aeed1eb48f", + "POST /messaging/{session}/messages/react": "d126f34d3d1336b0060f9321e4cb5f6d81fb54f75afe60b62c6c478345eda8b3", + "#/components/schemas/ReactRequest": "a97d67926f9fcce0f703654aeaa5805b3c89baac707b624374dcd7dfe074a22d", + "#/components/schemas/SendReactionResponse": "8de0bc201c1fcb15f766a55daa24e219ec2b91ce8a28f6758ffb0a53f299eb03", + "#/components/schemas/MessageReceipt": "a03d8eaf5edc18b0156aa0d41784dd2ecd69832e25f782564fc630371e3bcb4a", + "GET /messaging/projects/{projectSlug}/templates": "3bdf7aa2f992d9e0440148476fa6ad65961ee9510c9c16a4d9e779ca06a94b78", + "#/components/schemas/ListProjectTemplatesResponse": "e9c785aa85c147ee71b61c21b4dc2fd408a508ca33625c4a2369b280f41bfe94", + "#/components/schemas/MessageTemplate": "7280880879333454e0f12fe0821fc129c8de58fc1e15c998d8808a429e02ec8f", + "GET /messaging/projects/{projectSlug}/templates/{id}": "e594f033929c61980e66dad427f3bdee5ba8fe49574425f807e028c3fa7fb65b", + "#/components/schemas/GetProjectTemplateResponse": "3a266572c06eed33e7a39c89b77230060c3314a1ab4eee511cfc0df6d6bbc67f" + }, + "platform": { + "GET /platform/sessions": "dab70dab3c2c738345e7a5bc355deb9efc5cbe614c684d9bcc36928e5152fc81", + "#/components/schemas/PlatformSession": "a469d4c1af411d11d48d13923c96c89496f96df8e2c10cf9d122a7b2fb842761", + "#/components/schemas/ProjectId": "fdccf22724eac520992fae4b95b48d6309b1fa46dbdf0031873afb384a7ae282", + "#/components/schemas/SessionIdentifier": "67cdaffd7124ebe30e3ed110c7a1b851cbab3b4888ad6fc59d054d096fb65515", + "#/components/responses/BadRequest": "3a84c67f0face498ff4bf02ce59fa3e76cf590da59b02ba07bae25df3a05e157", + "#/components/schemas/ErrorEnvelope": "cf6263c6ed85daf3d68f5dfe62e5a49bc7ff5745ab9f13112a89ffba0661f702", + "#/components/responses/Unauthorized": "28e2654ec1441f573c15fcc22de9c88ac07a38ca550be07c6171a42352c918ca", + "#/components/responses/Forbidden": "9cbec049fedd88431891e560a7402126e29f516216812546d78f694391011713", + "GET /platform/projects/{projectId}/webhooks": "d8e86e19a0a1d07e1a12c2b047ea22b215da71805c712a563f731d33922b867c", + "#/components/schemas/ProjectWebhook": "8e9579e600f25f638b5e3c9dbd3504543ca36be5c800e8d64bb86a02075fa348", + "#/components/schemas/WebhookRetryPolicy": "a953fbcee54bed638f93e85b00670d4563f4ff8d17b80d456c831e2a7b0dd9f5", + "#/components/schemas/WebhookHeaderMetadata": "f03e715b0afdde824ab991f4a664cddfaed3586081926e0fca7b5d6c1440a366", + "#/components/schemas/WebhookSigningSecretMetadata": "0fed4dc6a07f2066215081784225210e7c0025c864fddfdc77eeca232d1b40b5", + "#/components/schemas/CursorPageMetadata": "a491dc0e4a93fd2ce4925bade45affaf3bcc840f905251a5b9c643f4b64e35da", + "#/components/responses/NotFound": "61d43cb4a9d7b627a86f47462d5b04413109bfe31a967cbbaf69919204091a67", + "POST /platform/projects/{projectId}/webhooks": "4299c2c587a455349b5ec2ce70442f4ba8dd910b6a922e0ccc94323af545a0dd", + "#/components/schemas/CreateProjectWebhookInput": "8332551baf79f9adca43bfb8261c23677090d5931b716cdf08107876e5d09522", + "#/components/schemas/WebhookRetryPolicyInput": "a953fbcee54bed638f93e85b00670d4563f4ff8d17b80d456c831e2a7b0dd9f5", + "#/components/schemas/WebhookHeaderInput": "9439ff800367858e22668035c1e432e1023d52fc39d65cfdc83a17068c989a51", + "#/components/schemas/ProjectWebhookCreationReceipt": "8fa37c1b86463fb4e451e456ac2b4d0afda26c8648407b937ed7ef22dc1d8147", + "#/components/schemas/IdempotencyReceipt": "20651991bf1da3fa2a4662f6672727d81896a0d3d931f1a30eeb926c8326a1c9", + "#/components/responses/Conflict": "88bc9c23f816071e5834b751fa69b1d8e37e21381f6eacddf29a9276c3a656c7", + "GET /platform/projects/{projectId}/webhooks/{webhookId}": "cef68b5bddb2010ac270e0816d2c74e56145009ff27c700aba695beebc257ab0", + "DELETE /platform/projects/{projectId}/webhooks/{webhookId}": "8da0a9d120858a05b0ab20e1d52f2951418305112475c8ca1b3b5b0fa793c89a", + "#/components/schemas/ProjectWebhookDeletionReceipt": "b3a9d35885f3523ae1789e95230009ff1749f6f34dced9359a09703f1c2289e5" + } + } +} diff --git a/contracts/source.json b/contracts/source.json new file mode 100644 index 0000000..c1deaa4 --- /dev/null +++ b/contracts/source.json @@ -0,0 +1,30 @@ +{ + "apiVersion": "2026-09-22", + "apiSource": "1307601eb7c2eb5e29715d2c1784e38e01e61433", + "sdk": { + "repository": "polymorfa/sdks", + "source": "b3bd05843342275f25f304bfe1fc67ca872740ba", + "publishedPackage": null + }, + "specs": { + "messaging": { + "publicSourcePath": "docs/api-reference/openapi.json", + "publicSourceSha256": "4a7b397119f4722b3d2cdcae440a152ca7249ebbc054ed6ebd93c5821f0f28ce", + "snapshot": "contracts/openapi.messaging.json", + "sha256": "1a92c533626b793e4ed65ef585f7cba37e83a21c38c9bd517c7f1270a38e58c7", + "sdkSnapshotPath": "contracts/openapi.messaging.json" + }, + "platform": { + "publicSourcePath": "docs/api-reference/openapi.management.json", + "publicSourceSha256": "f2e7963d5ddb0070c58dfe049c63e40b8bfdd806b7443f3b670b1e888ee4f10f", + "snapshot": "contracts/openapi.platform.json", + "sha256": "34a7f677d02899a4a392f68468eca0ee5b3b004416337bb33f46301a3dd06984", + "sdkSnapshotPath": "contracts/openapi.platform.json" + } + }, + "audience": "published public exports only", + "acceptance": { + "staging": null, + "production": null + } +} diff --git a/fixtures/events.json b/fixtures/events.json new file mode 100644 index 0000000..2673ff8 --- /dev/null +++ b/fixtures/events.json @@ -0,0 +1,27 @@ +{ + "messageReceived": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71", + "session": "739182640518203", "timestamp": "2026-11-01T12:00:00.000Z", "event": "message.received", + "payload": { + "id": "739182640518204", "whatsapp_ids": { "linked_devices": "SYNTHETIC-EXAMPLE" }, + "conversation": { "id": "739182640518205" }, "fromMe": false, + "timestamp": 1793534400, "pushName": "Synthetic contact", "isGroup": false, + "type": "text", "text": "Synthetic setup sample. No customer message was read." + } + }, + "messageState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a72", + "session": "739182640518203", "timestamp": "2026-11-01T12:00:00.000Z", "event": "message.sent", + "payload": { "id": "739182640518204", "whatsapp_ids": { "linked_devices": "SYNTHETIC-EXAMPLE" }, "conversation": { "id": "739182640518205" }, "type": "text", "timestamp": 1793534400 } + }, + "numberState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a73", + "session": "739182640518203", "timestamp": "2026-11-01T12:00:00.000Z", "event": "session.status", + "payload": { "status": "connected" } + }, + "callEnded": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a74", + "session": "739182640518203", "timestamp": "2026-11-01T12:00:00.000Z", "event": "call.ended", + "payload": { "from": { "id": "739182640518205" }, "callId": "SYNTHETIC-CALL", "durationSeconds": 60, "reason": "ended", "direction": "inbound", "hadVideo": false } + } +} diff --git a/make/README.md b/make/README.md new file mode 100644 index 0000000..58cdcc4 --- /dev/null +++ b/make/README.md @@ -0,0 +1,36 @@ +# Polymorfa for Make + +Native Custom App source, not a published or approved Make app. `app.json` holds +app metadata; `base.json` holds shared request settings. Component directories +separate metadata, parameters, communication and interfaces for review/upload +through Make's app tooling. They are source artifacts, not a scenario blueprint. + +`functions/` contains generated custom IML functions, each below 5,000 characters. +Make must enable custom functions for the publisher. Local tests execute their +JavaScript with a small IML shim; they do not qualify Make's actual interpreter, +function bindings or private webhook parameter storage. + +Create the **project** connection, then the functions, modules and dedicated +webhooks under one Polymorfa-owned app. Keep API keys and derived receiver values +in private connection/webhook state. Map static and mappable parameter files in +the native editor. Real API upload/import and editor acceptance are pending. + +Connection tests require `sessions:read`. Sends/reactions need `messages:write`, +template lookup `templates:read`, and hooks `webhooks:read` plus `webhooks:manage`. +The universal **Make an API call** module permits only the public allowlist on +the fixed Polymorfa origin; it cannot call arbitrary hosts or change principals. + +Writes require a stable business idempotency key and saved ISO start time. Map +native files to base64 and MIME type within the 4 MiB decoded connector limit. +Use string IDs. Deduplicate project-wide events by their stable event ID before +effects. Stop and reconcile incomplete/ambiguous hook inventories; do not create +another endpoint blindly. + +The acceptance record must cover every module's success and error paths, field +mapping, file conversion, receiver authentication, actual IML evaluation, +pagination, retries, cleanup and secret redaction. Submit for public QA only +after those scenarios work. Make publication is irreversible; use supported +version/module controls for rollback. + +Make's execution and retention locations are separate from Polymorfa EU/Brazil +hosting. Configure both deliberately before processing customer content. diff --git a/make/app.json b/make/app.json new file mode 100644 index 0000000..d024a1a --- /dev/null +++ b/make/app.json @@ -0,0 +1,8 @@ +{ + "name": "polymorfa", + "label": "Polymorfa", + "version": 1, + "theme": "#7657ff", + "language": "en", + "description": "Send messages and receive project events with Polymorfa." +} diff --git a/make/base.json b/make/base.json new file mode 100644 index 0000000..23bccb6 --- /dev/null +++ b/make/base.json @@ -0,0 +1,20 @@ +{ + "baseUrl": "https://api.polymorfa.com", + "headers": { + "Authorization": "Bearer {{connection.apiKey}}", + "Polymorfa-Version": "2026-09-22", + "User-Agent": "polymorfa-make/0.1.0" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.body.headers", + "response.body.data.secret" + ] + }, + "response": { + "error": { + "message": "[{{statusCode}}] Polymorfa {{body.error.code}}" + } + } +} diff --git a/make/connections/project/communication.json b/make/connections/project/communication.json new file mode 100644 index 0000000..de9f9f6 --- /dev/null +++ b/make/connections/project/communication.json @@ -0,0 +1,23 @@ +{ + "url": "/platform/sessions", + "method": "GET", + "qs": { + "projectId": "{{parameters.projectId}}" + }, + "headers": { + "Authorization": "Bearer {{parameters.apiKey}}", + "Polymorfa-Version": "2026-09-22" + }, + "response": { + "valid": "{{statusCode = 200}}", + "metadata": { + "type": "text", + "value": "{{parameters.projectId}}" + } + }, + "log": { + "sanitize": [ + "request.headers.authorization" + ] + } +} diff --git a/make/connections/project/parameters.json b/make/connections/project/parameters.json new file mode 100644 index 0000000..c36f497 --- /dev/null +++ b/make/connections/project/parameters.json @@ -0,0 +1,14 @@ +[ + { + "name": "apiKey", + "type": "password", + "label": "Project Token", + "required": true + }, + { + "name": "projectId", + "type": "text", + "label": "Project ID", + "required": true + } +] diff --git a/make/functions/polymorfaAdvanced.js b/make/functions/polymorfaAdvanced.js new file mode 100644 index 0000000..feb0470 --- /dev/null +++ b/make/functions/polymorfaAdvanced.js @@ -0,0 +1,36 @@ +function polymorfaAdvanced(p, auth) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function object(value, name) { + let parsed = value; + try { if (typeof value === 'string') parsed = JSON.parse(value); } catch { throw new IntegrationError(`invalid_${name}_json`); } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new IntegrationError(`invalid_${name}`); + return parsed; +} +const ORIGIN = 'https://api.polymorfa.com'; +function advanced(p, auth) { + const path = required(p.path, 'path'); + if (!path.startsWith('/') || path.startsWith('//') || /[%\\?#\s]/.test(path) || path.split('/').some(x => x === '.' || x === '..')) throw new IntegrationError('unsupported_path'); + const method = p.method; + const allowed = (method === 'GET' && (path === '/platform/sessions' || /^\/messaging\/projects\/[A-Za-z0-9_-]+\/templates(?:\/[A-Za-z0-9_-]+)?$/.test(path))) || + (method === 'POST' && /^\/messaging\/[A-Za-z0-9_-]+\/messages\/(send|react)$/.test(path)); + if (!allowed) throw new IntegrationError('unsupported_operation'); + const body = method === 'POST' ? object(p.requestBody, 'request_body') : undefined; + if (body) { + const keys = path.endsWith('/react') ? ['conversation', 'id', 'reaction'] : ['conversation', 'isForwarded', 'mentions', 'quotedMessage', 'content']; + if (Object.keys(body).some(k => !keys.includes(k))) throw new IntegrationError('unsupported_request_field'); + } + return { method, url: ORIGIN + path, ...(body ? { body } : {}), ...(path === '/platform/sessions' ? { query: { projectId: auth.projectId } } : {}) }; +} +return advanced(p, auth); +} diff --git a/make/functions/polymorfaBody.js b/make/functions/polymorfaBody.js new file mode 100644 index 0000000..a544240 --- /dev/null +++ b/make/functions/polymorfaBody.js @@ -0,0 +1,57 @@ +function polymorfaBody(action, p) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +const MAX_MEDIA_BYTES = 4194304; +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function conversation(p) { + if (!['phoneNumber', 'id', 'bsuid'].includes(p.recipientType)) throw new IntegrationError('invalid_recipient_type'); + required(p.recipient, 'recipient'); + if (p.recipientType === 'phoneNumber' && !/^\+[1-9]\d{1,14}$/.test(p.recipient)) throw new IntegrationError('invalid_phone_number'); + if (p.recipientType === 'id' && !/^[1-9]\d{0,18}$/.test(p.recipient)) throw new IntegrationError('invalid_conversation_id'); + if (p.recipientType === 'bsuid' && !/^[A-Z]{2}\.[A-Za-z0-9]{1,128}$/.test(p.recipient)) throw new IntegrationError('invalid_bsuid'); + return { [p.recipientType]: p.recipient }; +} +function messageBody(action, p) { + const body = { conversation: conversation(p) }; + if (action === 'sendText') { + body.content = { text: required(p.text, 'text') }; + if (p.quotedMessageId) { + if (!/^[1-9]\d{0,18}$/.test(required(p.quotedMessageId, 'quoted_message_id'))) throw new IntegrationError('invalid_quoted_message_id'); + body.quotedMessage = { id: p.quotedMessageId }; + } + } else if (action === 'sendTemplate') { + let components; + try { components = typeof p.components === 'string' ? JSON.parse(p.components || '[]') : (p.components ?? []); } catch { throw new IntegrationError('invalid_components_json'); } + if (!Array.isArray(components) || components.length > 1000) throw new IntegrationError('invalid_components'); + body.content = { template: { name: required(p.templateName, 'template_name'), language: required(p.language, 'language'), components } }; + if (p.language.length < 2) throw new IntegrationError('invalid_language'); + } else if (action === 'sendMedia') { + if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); + required(p.media, 'media'); + if (p.mediaSource === 'url') { + let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } + if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + throw new IntegrationError('invalid_or_oversized_media'); + } + const media = { [p.mediaSource]: p.media }; + for (const key of ['mimeType', 'caption']) if (p[key]) media[key] = required(p[key], key); + if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); + body.content = { [p.mediaType]: media }; + } else if (action === 'react') { + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = p.reaction; + } else throw new IntegrationError('unknown_action'); + return body; +} +return messageBody(action, p); +} diff --git a/make/functions/polymorfaFindHook.js b/make/functions/polymorfaFindHook.js new file mode 100644 index 0000000..70db7fa --- /dev/null +++ b/make/functions/polymorfaFindHook.js @@ -0,0 +1,11 @@ +function polymorfaFindHook(body, auth, url, group, key) { +const expected = iml.polymorfaHook(auth, group, key); +const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; +if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.'); +if (body.data.some(h => h.url !== url && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.'); +const owns = h => h.projectId === auth.projectId && h.owner === 'project' && h.url === url && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t)); +if (body.data.some(h => (h.url === url || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.'); +const matches = body.data.filter(owns); +if (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.'); +return matches[0]?.id || null; +} diff --git a/make/functions/polymorfaHook.js b/make/functions/polymorfaHook.js new file mode 100644 index 0000000..dd91fda --- /dev/null +++ b/make/functions/polymorfaHook.js @@ -0,0 +1,26 @@ +function polymorfaHook(auth, group, subscriptionKey) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +const events = {"messageReceived":{"label":"New Message","description":"Triggers when a message is received.","types":["message.received"]},"messageState":{"label":"Message State Changed","description":"Triggers when a message is sent, acknowledged, failed or updated.","types":["message.sent","message.ack","message.failed","message.update"]},"numberState":{"label":"Number State Changed","description":"Triggers when a Number changes state, connects or logs out.","types":["session.status","session.connected","session.logged_out"]},"callEnded":{"label":"Call Ended","description":"Triggers when a call ends.","types":["call.ended"]}}; +if (!events[group]) throw new IntegrationError('invalid_event_group'); +required(subscriptionKey, 'subscription_key'); +if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); +const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); +const secret = iml.sha256(scope, 'hex', auth.apiKey); +return { name: 'X-Polymorfa-Integration-Token-' + secret.slice(0, 24), value: secret, key: secret }; +} diff --git a/make/functions/polymorfaOwnedHook.js b/make/functions/polymorfaOwnedHook.js new file mode 100644 index 0000000..379bee9 --- /dev/null +++ b/make/functions/polymorfaOwnedHook.js @@ -0,0 +1,6 @@ +function polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group) { +const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; +const h = body.data; +if (!h || h.owner !== 'project' || h.projectId !== projectId || h.url !== ownedUrl || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.'); +return true; +} diff --git a/make/functions/polymorfaReceive.js b/make/functions/polymorfaReceive.js new file mode 100644 index 0000000..bd4aa64 --- /dev/null +++ b/make/functions/polymorfaReceive.js @@ -0,0 +1,6 @@ +function polymorfaReceive(expectedName, expectedValue, group, headers, body) { +const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; +const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase()); +if (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false; +return !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload)); +} diff --git a/make/functions/polymorfaRequest.js b/make/functions/polymorfaRequest.js new file mode 100644 index 0000000..cec9d4c --- /dev/null +++ b/make/functions/polymorfaRequest.js @@ -0,0 +1,44 @@ +function polymorfaRequest(action, p, auth) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +const ORIGIN = 'https://api.polymorfa.com', VERSION = '2026-09-22', MAX_WRITE_AGE = 82800000; +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +function headers(auth, provider) { + const token = required(auth.apiKey, 'project_token'); + if (/[\r\n]/.test(token)) throw new IntegrationError('invalid_project_token'); + segment(auth.projectId, 'project_id'); + return { Authorization: `Bearer ${token}`, 'Polymorfa-Version': VERSION, 'Content-Type': 'application/json', 'User-Agent': `polymorfa-${provider}/0.1.0` }; +} +function writeHeaders(p, now = Date.now()) { + const key = required(p.idempotencyKey, 'idempotency_key'); + if (Buffer.byteLength(key, 'utf8') > 255 || /[\x00-\x1f\x7f]/.test(key)) throw new IntegrationError('invalid_idempotency_key'); + const created = Date.parse(required(p.writeCreatedAt, 'write_started_at')); + if (!Number.isFinite(created) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(p.writeCreatedAt) || created > now + 60000 || now - created >= MAX_WRITE_AGE) throw new IntegrationError('write_requires_reconciliation'); + return { 'Idempotency-Key': key }; +} +function buildRequest(action, p, auth, provider = 'integration', now) { + let r; + if (['sendText', 'sendTemplate', 'sendMedia', 'react'].includes(action)) r = { method: 'POST', url: `${ORIGIN}/messaging/${segment(p.session, 'number')}/messages/${action === 'react' ? 'react' : 'send'}`, body: iml.polymorfaBody(action, p) }; + else if (action === 'listNumbers') r = { method: 'GET', url: ORIGIN + '/platform/sessions', query: { projectId: auth.projectId } }; + else if (action === 'listTemplates') r = { method: 'GET', url: `${ORIGIN}/messaging/projects/${segment(p.projectSlug, 'project_slug')}/templates` }; + else if (action === 'apiRequest') r = iml.polymorfaAdvanced(p, auth); + else throw new IntegrationError('unknown_action'); + r.headers = { ...headers(auth, provider), ...(r.method === 'POST' ? writeHeaders(p, now) : {}) }; + return r; +} +return buildRequest(action, p, auth, 'make'); +} diff --git a/make/functions/polymorfaResult.js b/make/functions/polymorfaResult.js new file mode 100644 index 0000000..491979f --- /dev/null +++ b/make/functions/polymorfaResult.js @@ -0,0 +1,25 @@ +function polymorfaResult(status, headers, body) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; } +function parseResponse(response) { + const status = response.status; + let body = response.body; + try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + if (status < 200 || status >= 300) { + const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); + const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); + const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + } + if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); + return body; +} +return parseResponse({ status, headers, body }); +} diff --git a/make/functions/polymorfaSegment.js b/make/functions/polymorfaSegment.js new file mode 100644 index 0000000..a47c4ef --- /dev/null +++ b/make/functions/polymorfaSegment.js @@ -0,0 +1,20 @@ +function polymorfaSegment(value) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +return segment(value, 'resource_id'); +} diff --git a/make/modules/apiRequest/communication.json b/make/modules/apiRequest/communication.json new file mode 100644 index 0000000..dcef270 --- /dev/null +++ b/make/modules/apiRequest/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('apiRequest'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('apiRequest'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('apiRequest'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('apiRequest'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('apiRequest'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/apiRequest/interface.json b/make/modules/apiRequest/interface.json new file mode 100644 index 0000000..ee49ef7 --- /dev/null +++ b/make/modules/apiRequest/interface.json @@ -0,0 +1,7 @@ +[ + { + "name": "data", + "label": "Result", + "type": "collection" + } +] diff --git a/make/modules/apiRequest/metadata.json b/make/modules/apiRequest/metadata.json new file mode 100644 index 0000000..76a9ca4 --- /dev/null +++ b/make/modules/apiRequest/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Make an API call", + "description": "Performs an arbitrary authorized API call.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/apiRequest/parameters.json b/make/modules/apiRequest/parameters.json new file mode 100644 index 0000000..15edf64 --- /dev/null +++ b/make/modules/apiRequest/parameters.json @@ -0,0 +1,48 @@ +[ + { + "name": "method", + "type": "select", + "label": "Method", + "required": true, + "default": "GET", + "options": [ + { + "value": "GET", + "label": "GET" + }, + { + "value": "POST", + "label": "POST" + } + ] + }, + { + "name": "path", + "type": "text", + "label": "Path", + "required": true, + "help": "A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused." + }, + { + "name": "requestBody", + "type": "text", + "label": "Request Body", + "required": false, + "default": "{}", + "help": "JSON object. Authentication and version headers are supplied by the connection." + }, + { + "name": "idempotencyKey", + "type": "text", + "label": "Idempotency Key", + "required": false, + "help": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + { + "name": "writeCreatedAt", + "type": "text", + "label": "Write Started At", + "required": false, + "help": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +] diff --git a/make/modules/callEnded/interface.json b/make/modules/callEnded/interface.json new file mode 100644 index 0000000..b683ae9 --- /dev/null +++ b/make/modules/callEnded/interface.json @@ -0,0 +1,96 @@ +[ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "session", + "label": "session", + "type": "text" + }, + { + "name": "externalId", + "label": "externalId", + "type": "text" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "text" + }, + { + "name": "event", + "label": "event", + "type": "text" + }, + { + "name": "payload", + "label": "payload", + "type": "collection", + "spec": [ + { + "name": "from", + "label": "from", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + } + ] + }, + { + "name": "callId", + "label": "callId", + "type": "text" + }, + { + "name": "durationSeconds", + "label": "durationSeconds", + "type": "number" + }, + { + "name": "reason", + "label": "reason", + "type": "text" + }, + { + "name": "direction", + "label": "direction", + "type": "text" + }, + { + "name": "hadVideo", + "label": "hadVideo", + "type": "boolean" + }, + { + "name": "sessionConnection", + "label": "sessionConnection", + "type": "text" + } + ] + } +] diff --git a/make/modules/callEnded/metadata.json b/make/modules/callEnded/metadata.json new file mode 100644 index 0000000..9e0486b --- /dev/null +++ b/make/modules/callEnded/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Call Ended", + "description": "Triggers when a call ends.", + "type": "instant", + "webhook": "callEnded" +} diff --git a/make/modules/listNumbers/communication.json b/make/modules/listNumbers/communication.json new file mode 100644 index 0000000..d0a47a9 --- /dev/null +++ b/make/modules/listNumbers/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('listNumbers'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('listNumbers'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('listNumbers'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('listNumbers'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('listNumbers'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/listNumbers/interface.json b/make/modules/listNumbers/interface.json new file mode 100644 index 0000000..8e782c0 --- /dev/null +++ b/make/modules/listNumbers/interface.json @@ -0,0 +1,141 @@ +[ + { + "name": "data", + "label": "data", + "type": "array", + "spec": { + "type": "collection", + "spec": [ + { + "name": "_id", + "label": "_id", + "type": "text" + }, + { + "name": "_creationTime", + "label": "_creationTime", + "type": "number" + }, + { + "name": "projectId", + "label": "projectId", + "type": "text" + }, + { + "name": "sessionId", + "label": "sessionId", + "type": "text" + }, + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "phone", + "label": "phone", + "type": "text" + }, + { + "name": "platform", + "label": "platform", + "type": "text" + }, + { + "name": "isBusiness", + "label": "isBusiness", + "type": "boolean" + }, + { + "name": "testMode", + "label": "testMode", + "type": "boolean" + }, + { + "name": "tierOverride", + "label": "tierOverride", + "type": "text" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "statusReason", + "label": "statusReason", + "type": "text" + }, + { + "name": "messageCount", + "label": "messageCount", + "type": "number" + }, + { + "name": "lastActiveAt", + "label": "lastActiveAt", + "type": "number" + }, + { + "name": "paidUntil", + "label": "paidUntil", + "type": "number" + }, + { + "name": "customer", + "label": "customer", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "isDefault", + "label": "isDefault", + "type": "boolean" + } + ] + }, + { + "name": "claim", + "label": "claim", + "type": "collection", + "spec": [ + { + "name": "kind", + "label": "kind", + "type": "text" + }, + { + "name": "displayName", + "label": "displayName", + "type": "text" + }, + { + "name": "avatarUrl", + "label": "avatarUrl", + "type": "text" + }, + { + "name": "avatarSeed", + "label": "avatarSeed", + "type": "text" + }, + { + "name": "claimedAt", + "label": "claimedAt", + "type": "number" + } + ] + } + ] + } + } +] diff --git a/make/modules/listNumbers/metadata.json b/make/modules/listNumbers/metadata.json new file mode 100644 index 0000000..280f699 --- /dev/null +++ b/make/modules/listNumbers/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "List Numbers", + "description": "Lists Numbers visible to the connected project.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/listNumbers/parameters.json b/make/modules/listNumbers/parameters.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/make/modules/listNumbers/parameters.json @@ -0,0 +1 @@ +[] diff --git a/make/modules/listTemplates/communication.json b/make/modules/listTemplates/communication.json new file mode 100644 index 0000000..5d071ab --- /dev/null +++ b/make/modules/listTemplates/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('listTemplates'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('listTemplates'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('listTemplates'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('listTemplates'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('listTemplates'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/listTemplates/interface.json b/make/modules/listTemplates/interface.json new file mode 100644 index 0000000..d10fc13 --- /dev/null +++ b/make/modules/listTemplates/interface.json @@ -0,0 +1,72 @@ +[ + { + "name": "success", + "label": "success", + "type": "boolean" + }, + { + "name": "data", + "label": "data", + "type": "array", + "spec": { + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "category", + "label": "category", + "type": "text" + }, + { + "name": "language", + "label": "language", + "type": "text" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "kind", + "label": "kind", + "type": "text" + }, + { + "name": "definition", + "label": "definition", + "type": "text" + }, + { + "name": "sampleValues", + "label": "sampleValues", + "type": "text" + }, + { + "name": "cloudLinks", + "label": "cloudLinks", + "type": "array" + }, + { + "name": "createdAt", + "label": "createdAt", + "type": "number" + }, + { + "name": "updatedAt", + "label": "updatedAt", + "type": "number" + } + ] + } + } +] diff --git a/make/modules/listTemplates/metadata.json b/make/modules/listTemplates/metadata.json new file mode 100644 index 0000000..af73267 --- /dev/null +++ b/make/modules/listTemplates/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "List Templates", + "description": "Lists message templates for a project.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/listTemplates/parameters.json b/make/modules/listTemplates/parameters.json new file mode 100644 index 0000000..f7973f9 --- /dev/null +++ b/make/modules/listTemplates/parameters.json @@ -0,0 +1,8 @@ +[ + { + "name": "projectSlug", + "type": "text", + "label": "Project Slug", + "required": true + } +] diff --git a/make/modules/messageReceived/interface.json b/make/modules/messageReceived/interface.json new file mode 100644 index 0000000..fcad85f --- /dev/null +++ b/make/modules/messageReceived/interface.json @@ -0,0 +1,410 @@ +[ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "session", + "label": "session", + "type": "text" + }, + { + "name": "externalId", + "label": "externalId", + "type": "text" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "text" + }, + { + "name": "event", + "label": "event", + "type": "text" + }, + { + "name": "payload", + "label": "payload", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection" + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + }, + { + "name": "sender", + "label": "sender", + "type": "collection" + } + ] + }, + { + "name": "fromMe", + "label": "fromMe", + "type": "boolean" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "number" + }, + { + "name": "pushName", + "label": "pushName", + "type": "text" + }, + { + "name": "isGroup", + "label": "isGroup", + "type": "boolean" + }, + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "ptt", + "label": "ptt", + "type": "boolean" + }, + { + "name": "filename", + "label": "filename", + "type": "text" + }, + { + "name": "latitude", + "label": "latitude", + "type": "number" + }, + { + "name": "longitude", + "label": "longitude", + "type": "number" + }, + { + "name": "displayName", + "label": "displayName", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "reaction", + "label": "reaction", + "type": "text" + }, + { + "name": "reactionTo", + "label": "reactionTo", + "type": "text" + }, + { + "name": "revokedId", + "label": "revokedId", + "type": "text" + }, + { + "name": "nativeFlowResponse", + "label": "nativeFlowResponse", + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "paramsJson", + "label": "paramsJson", + "type": "text" + }, + { + "name": "version", + "label": "version", + "type": "number" + } + ] + }, + { + "name": "replyChoice", + "label": "replyChoice", + "type": "collection", + "spec": [ + { + "name": "kind", + "label": "kind", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + } + ] + }, + { + "name": "parentMessageId", + "label": "parentMessageId", + "type": "text" + }, + { + "name": "media", + "label": "media", + "type": "text" + }, + { + "name": "mediaUrl", + "label": "mediaUrl", + "type": "text" + }, + { + "name": "edited", + "label": "edited", + "type": "boolean" + }, + { + "name": "pollOptions", + "label": "pollOptions", + "type": "array" + }, + { + "name": "unavailable", + "label": "unavailable", + "type": "boolean" + }, + { + "name": "unavailableReason", + "label": "unavailableReason", + "type": "text" + }, + { + "name": "width", + "label": "width", + "type": "number" + }, + { + "name": "height", + "label": "height", + "type": "number" + }, + { + "name": "seconds", + "label": "seconds", + "type": "number" + }, + { + "name": "waveform", + "label": "waveform", + "type": "array" + }, + { + "name": "pageCount", + "label": "pageCount", + "type": "number" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "collection", + "spec": [ + { + "name": "contentType", + "label": "contentType", + "type": "text" + }, + { + "name": "data", + "label": "data", + "type": "text" + }, + { + "name": "width", + "label": "width", + "type": "number" + }, + { + "name": "height", + "label": "height", + "type": "number" + } + ] + }, + { + "name": "linkPreview", + "label": "linkPreview", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "previewType", + "label": "previewType", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "collection" + } + ] + }, + { + "name": "association", + "label": "association", + "type": "collection", + "spec": [ + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "parentMessageId", + "label": "parentMessageId", + "type": "text" + } + ] + }, + { + "name": "quality", + "label": "quality", + "type": "text" + }, + { + "name": "senderName", + "label": "senderName", + "type": "text" + }, + { + "name": "interactive", + "label": "interactive", + "type": "collection" + }, + { + "name": "order", + "label": "order", + "type": "collection", + "spec": [ + { + "name": "catalogId", + "label": "catalogId", + "type": "text" + }, + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "items", + "label": "items", + "type": "array" + } + ] + }, + { + "name": "referral", + "label": "referral", + "type": "collection", + "spec": [ + { + "name": "source_type", + "label": "source_type", + "type": "text" + }, + { + "name": "source_id", + "label": "source_id", + "type": "text" + }, + { + "name": "source_url", + "label": "source_url", + "type": "text" + }, + { + "name": "ctwa_clid", + "label": "ctwa_clid", + "type": "text" + } + ] + } + ] + } +] diff --git a/make/modules/messageReceived/metadata.json b/make/modules/messageReceived/metadata.json new file mode 100644 index 0000000..5396075 --- /dev/null +++ b/make/modules/messageReceived/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "New Message", + "description": "Triggers when a message is received.", + "type": "instant", + "webhook": "messageReceived" +} diff --git a/make/modules/messageState/interface.json b/make/modules/messageState/interface.json new file mode 100644 index 0000000..ed8250e --- /dev/null +++ b/make/modules/messageState/interface.json @@ -0,0 +1,602 @@ +[ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "session", + "label": "session", + "type": "text" + }, + { + "name": "externalId", + "label": "externalId", + "type": "text" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "text" + }, + { + "name": "event", + "label": "event", + "type": "text" + }, + { + "name": "payload", + "label": "payload", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + }, + { + "name": "sender", + "label": "sender", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + } + ] + } + ] + }, + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "number" + }, + { + "name": "width", + "label": "width", + "type": "number" + }, + { + "name": "height", + "label": "height", + "type": "number" + }, + { + "name": "seconds", + "label": "seconds", + "type": "number" + }, + { + "name": "waveform", + "label": "waveform", + "type": "array" + }, + { + "name": "pageCount", + "label": "pageCount", + "type": "number" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "collection", + "spec": [ + { + "name": "contentType", + "label": "contentType", + "type": "text" + }, + { + "name": "data", + "label": "data", + "type": "text" + }, + { + "name": "width", + "label": "width", + "type": "number" + }, + { + "name": "height", + "label": "height", + "type": "number" + } + ] + }, + { + "name": "linkPreview", + "label": "linkPreview", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "previewType", + "label": "previewType", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "collection", + "spec": [ + { + "name": "contentType", + "label": "contentType", + "type": "text" + }, + { + "name": "data", + "label": "data", + "type": "text" + }, + { + "name": "width", + "label": "width", + "type": "number" + }, + { + "name": "height", + "label": "height", + "type": "number" + } + ] + } + ] + }, + { + "name": "association", + "label": "association", + "type": "collection", + "spec": [ + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "parentMessageId", + "label": "parentMessageId", + "type": "text" + } + ] + }, + { + "name": "quality", + "label": "quality", + "type": "text" + }, + { + "name": "messages", + "label": "messages", + "type": "array", + "spec": { + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + } + ] + } + }, + { + "name": "from", + "label": "from", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + } + ] + }, + { + "name": "sender", + "label": "sender", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + } + ] + }, + { + "name": "pricing", + "label": "pricing", + "type": "collection", + "spec": [ + { + "name": "billable", + "label": "billable", + "type": "boolean" + }, + { + "name": "pricing_model", + "label": "pricing_model", + "type": "text" + }, + { + "name": "category", + "label": "category", + "type": "text" + }, + { + "name": "type", + "label": "type", + "type": "text" + } + ] + }, + { + "name": "to", + "label": "to", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + }, + { + "name": "parentBsuid", + "label": "parentBsuid", + "type": "text" + } + ] + }, + { + "name": "error", + "label": "error", + "type": "text" + }, + { + "name": "code", + "label": "code", + "type": "text" + }, + { + "name": "retryAfter", + "label": "retryAfter", + "type": "number" + }, + { + "name": "fromMe", + "label": "fromMe", + "type": "boolean" + }, + { + "name": "pushName", + "label": "pushName", + "type": "text" + }, + { + "name": "isGroup", + "label": "isGroup", + "type": "boolean" + }, + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "ptt", + "label": "ptt", + "type": "boolean" + }, + { + "name": "filename", + "label": "filename", + "type": "text" + }, + { + "name": "latitude", + "label": "latitude", + "type": "number" + }, + { + "name": "longitude", + "label": "longitude", + "type": "number" + }, + { + "name": "displayName", + "label": "displayName", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "reaction", + "label": "reaction", + "type": "text" + }, + { + "name": "reactionTo", + "label": "reactionTo", + "type": "text" + }, + { + "name": "revokedId", + "label": "revokedId", + "type": "text" + }, + { + "name": "nativeFlowResponse", + "label": "nativeFlowResponse", + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "paramsJson", + "label": "paramsJson", + "type": "text" + }, + { + "name": "version", + "label": "version", + "type": "number" + } + ] + }, + { + "name": "replyChoice", + "label": "replyChoice", + "type": "collection", + "spec": [ + { + "name": "kind", + "label": "kind", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + } + ] + }, + { + "name": "parentMessageId", + "label": "parentMessageId", + "type": "text" + }, + { + "name": "media", + "label": "media", + "type": "text" + }, + { + "name": "mediaUrl", + "label": "mediaUrl", + "type": "text" + }, + { + "name": "edited", + "label": "edited", + "type": "boolean" + }, + { + "name": "pollOptions", + "label": "pollOptions", + "type": "array", + "spec": { + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "hash", + "label": "hash", + "type": "text" + } + ] + } + }, + { + "name": "unavailable", + "label": "unavailable", + "type": "boolean" + }, + { + "name": "unavailableReason", + "label": "unavailableReason", + "type": "text" + } + ] + } +] diff --git a/make/modules/messageState/metadata.json b/make/modules/messageState/metadata.json new file mode 100644 index 0000000..2cc2c17 --- /dev/null +++ b/make/modules/messageState/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Message State Changed", + "description": "Triggers when a message is sent, acknowledged, failed or updated.", + "type": "instant", + "webhook": "messageState" +} diff --git a/make/modules/numberState/interface.json b/make/modules/numberState/interface.json new file mode 100644 index 0000000..ff1fe0c --- /dev/null +++ b/make/modules/numberState/interface.json @@ -0,0 +1,124 @@ +[ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "session", + "label": "session", + "type": "text" + }, + { + "name": "externalId", + "label": "externalId", + "type": "text" + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "text" + }, + { + "name": "event", + "label": "event", + "type": "text" + }, + { + "name": "payload", + "label": "payload", + "type": "collection", + "spec": [ + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "statusReason", + "label": "statusReason", + "type": "text" + }, + { + "name": "banCode", + "label": "banCode", + "type": "number" + }, + { + "name": "banReason", + "label": "banReason", + "type": "text" + }, + { + "name": "banExpiresAt", + "label": "banExpiresAt", + "type": "number" + }, + { + "name": "detail", + "label": "detail", + "type": "text" + }, + { + "name": "source", + "label": "source", + "type": "text" + }, + { + "name": "kind", + "label": "kind", + "type": "text" + }, + { + "name": "wabaId", + "label": "wabaId", + "type": "text" + }, + { + "name": "value", + "label": "value", + "type": "collection" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "pushName", + "label": "pushName", + "type": "text" + }, + { + "name": "phonePlatform", + "label": "phonePlatform", + "type": "text" + }, + { + "name": "accountType", + "label": "accountType", + "type": "text" + }, + { + "name": "businessName", + "label": "businessName", + "type": "text" + }, + { + "name": "reason", + "label": "reason", + "type": "text" + }, + { + "name": "code", + "label": "code", + "type": "number" + } + ] + } +] diff --git a/make/modules/numberState/metadata.json b/make/modules/numberState/metadata.json new file mode 100644 index 0000000..e834c46 --- /dev/null +++ b/make/modules/numberState/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Number State Changed", + "description": "Triggers when a Number changes state, connects or logs out.", + "type": "instant", + "webhook": "numberState" +} diff --git a/make/modules/react/communication.json b/make/modules/react/communication.json new file mode 100644 index 0000000..06db812 --- /dev/null +++ b/make/modules/react/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('react'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('react'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('react'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('react'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('react'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/react/interface.json b/make/modules/react/interface.json new file mode 100644 index 0000000..26795e5 --- /dev/null +++ b/make/modules/react/interface.json @@ -0,0 +1,78 @@ +[ + { + "name": "success", + "label": "success", + "type": "boolean" + }, + { + "name": "data", + "label": "data", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + } + ] + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "date" + }, + { + "name": "status", + "label": "status", + "type": "text" + } + ] + } +] diff --git a/make/modules/react/metadata.json b/make/modules/react/metadata.json new file mode 100644 index 0000000..b987c7d --- /dev/null +++ b/make/modules/react/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "React to Message", + "description": "Adds or removes a reaction on a message.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/react/parameters.json b/make/modules/react/parameters.json new file mode 100644 index 0000000..0c43e6a --- /dev/null +++ b/make/modules/react/parameters.json @@ -0,0 +1,64 @@ +[ + { + "name": "session", + "type": "text", + "label": "Number", + "required": true, + "help": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + { + "name": "recipientType", + "type": "select", + "label": "Recipient Type", + "required": true, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + { + "name": "recipient", + "type": "text", + "label": "Recipient", + "required": true, + "help": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + { + "name": "messageId", + "type": "text", + "label": "Message ID", + "required": true + }, + { + "name": "reaction", + "type": "text", + "label": "Reaction", + "required": true, + "help": "An emoji, or an empty string to remove the reaction." + }, + { + "name": "idempotencyKey", + "type": "text", + "label": "Idempotency Key", + "required": true, + "help": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + { + "name": "writeCreatedAt", + "type": "text", + "label": "Write Started At", + "required": true, + "help": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +] diff --git a/make/modules/sendMedia/communication.json b/make/modules/sendMedia/communication.json new file mode 100644 index 0000000..2ad6416 --- /dev/null +++ b/make/modules/sendMedia/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('sendMedia'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('sendMedia'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('sendMedia'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('sendMedia'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('sendMedia'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/sendMedia/interface.json b/make/modules/sendMedia/interface.json new file mode 100644 index 0000000..1585677 --- /dev/null +++ b/make/modules/sendMedia/interface.json @@ -0,0 +1,669 @@ +[ + { + "name": "success", + "label": "success", + "type": "boolean" + }, + { + "name": "data", + "label": "data", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + } + ] + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "date" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "content", + "label": "content", + "type": "collection", + "spec": [ + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "image", + "label": "image", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + }, + { + "name": "quality", + "label": "quality", + "type": "text" + } + ] + }, + { + "name": "video", + "label": "video", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "file", + "label": "file", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "filename", + "label": "filename", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "voice", + "label": "voice", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "ptt", + "label": "ptt", + "type": "boolean" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "sticker", + "label": "sticker", + "type": "collection", + "spec": [ + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "location", + "label": "location", + "type": "collection", + "spec": [ + { + "name": "lat", + "label": "lat", + "type": "number" + }, + { + "name": "long", + "label": "long", + "type": "number" + }, + { + "name": "address", + "label": "address", + "type": "text" + } + ] + }, + { + "name": "poll", + "label": "poll", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "options", + "label": "options", + "type": "array" + }, + { + "name": "multiSelect", + "label": "multiSelect", + "type": "boolean" + } + ] + }, + { + "name": "contact", + "label": "contact", + "type": "collection", + "spec": [ + { + "name": "vcard", + "label": "vcard", + "type": "text" + } + ] + }, + { + "name": "requestPhoneNumber", + "label": "requestPhoneNumber", + "type": "collection" + }, + { + "name": "product", + "label": "product", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "currencyCode", + "label": "currencyCode", + "type": "text" + }, + { + "name": "priceAmount1000", + "label": "priceAmount1000", + "type": "number" + }, + { + "name": "salePriceAmount1000", + "label": "salePriceAmount1000", + "type": "number" + }, + { + "name": "retailerId", + "label": "retailerId", + "type": "text" + }, + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "imageCount", + "label": "imageCount", + "type": "number" + }, + { + "name": "image", + "label": "image", + "type": "collection" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + } + ] + }, + { + "name": "productList", + "label": "productList", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "order", + "label": "order", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "thumbnailBase64", + "label": "thumbnailBase64", + "type": "text" + }, + { + "name": "itemCount", + "label": "itemCount", + "type": "number" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "message", + "label": "message", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "sellerId", + "label": "sellerId", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "totalAmount1000", + "label": "totalAmount1000", + "type": "number" + }, + { + "name": "totalCurrencyCode", + "label": "totalCurrencyCode", + "type": "text" + }, + { + "name": "catalogType", + "label": "catalogType", + "type": "text" + } + ] + }, + { + "name": "list", + "label": "list", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "buttons", + "label": "buttons", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "buttons", + "label": "buttons", + "type": "array" + } + ] + }, + { + "name": "addressMessage", + "label": "addressMessage", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "country", + "label": "country", + "type": "text" + } + ] + }, + { + "name": "flow", + "label": "flow", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "action", + "label": "action", + "type": "text" + }, + { + "name": "screen", + "label": "screen", + "type": "text" + }, + { + "name": "dataJson", + "label": "dataJson", + "type": "text" + } + ] + }, + { + "name": "callPermissionRequest", + "label": "callPermissionRequest", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + } + ] + }, + { + "name": "template", + "label": "template", + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "language", + "label": "language", + "type": "text" + }, + { + "name": "components", + "label": "components", + "type": "array" + } + ] + } + ] + }, + { + "name": "mediaId", + "label": "mediaId", + "type": "text" + }, + { + "name": "hdMessageId", + "label": "hdMessageId", + "type": "text" + } + ] + } +] diff --git a/make/modules/sendMedia/metadata.json b/make/modules/sendMedia/metadata.json new file mode 100644 index 0000000..0448129 --- /dev/null +++ b/make/modules/sendMedia/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Send Media", + "description": "Sends an image, video, file or voice message.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/sendMedia/parameters.json b/make/modules/sendMedia/parameters.json new file mode 100644 index 0000000..517a4f3 --- /dev/null +++ b/make/modules/sendMedia/parameters.json @@ -0,0 +1,119 @@ +[ + { + "name": "session", + "type": "text", + "label": "Number", + "required": true, + "help": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + { + "name": "recipientType", + "type": "select", + "label": "Recipient Type", + "required": true, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + { + "name": "recipient", + "type": "text", + "label": "Recipient", + "required": true, + "help": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + { + "name": "mediaType", + "type": "select", + "label": "Media Type", + "required": true, + "default": "image", + "options": [ + { + "value": "image", + "label": "Image" + }, + { + "value": "video", + "label": "Video" + }, + { + "value": "file", + "label": "File" + }, + { + "value": "voice", + "label": "Voice" + } + ] + }, + { + "name": "mediaSource", + "type": "select", + "label": "Media Source", + "required": true, + "default": "url", + "options": [ + { + "value": "url", + "label": "HTTPS URL" + }, + { + "value": "base64", + "label": "Base64 Bytes" + } + ] + }, + { + "name": "media", + "type": "text", + "label": "Media", + "required": true, + "help": "HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower." + }, + { + "name": "mimeType", + "type": "text", + "label": "MIME Type", + "required": false + }, + { + "name": "caption", + "type": "text", + "label": "Caption", + "required": false + }, + { + "name": "filename", + "type": "text", + "label": "Filename", + "required": false, + "help": "Used for file messages." + }, + { + "name": "idempotencyKey", + "type": "text", + "label": "Idempotency Key", + "required": true, + "help": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + { + "name": "writeCreatedAt", + "type": "text", + "label": "Write Started At", + "required": true, + "help": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +] diff --git a/make/modules/sendTemplate/communication.json b/make/modules/sendTemplate/communication.json new file mode 100644 index 0000000..d24bc66 --- /dev/null +++ b/make/modules/sendTemplate/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('sendTemplate'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('sendTemplate'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('sendTemplate'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('sendTemplate'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('sendTemplate'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/sendTemplate/interface.json b/make/modules/sendTemplate/interface.json new file mode 100644 index 0000000..1585677 --- /dev/null +++ b/make/modules/sendTemplate/interface.json @@ -0,0 +1,669 @@ +[ + { + "name": "success", + "label": "success", + "type": "boolean" + }, + { + "name": "data", + "label": "data", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + } + ] + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "date" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "content", + "label": "content", + "type": "collection", + "spec": [ + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "image", + "label": "image", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + }, + { + "name": "quality", + "label": "quality", + "type": "text" + } + ] + }, + { + "name": "video", + "label": "video", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "file", + "label": "file", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "filename", + "label": "filename", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "voice", + "label": "voice", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "ptt", + "label": "ptt", + "type": "boolean" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "sticker", + "label": "sticker", + "type": "collection", + "spec": [ + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "location", + "label": "location", + "type": "collection", + "spec": [ + { + "name": "lat", + "label": "lat", + "type": "number" + }, + { + "name": "long", + "label": "long", + "type": "number" + }, + { + "name": "address", + "label": "address", + "type": "text" + } + ] + }, + { + "name": "poll", + "label": "poll", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "options", + "label": "options", + "type": "array" + }, + { + "name": "multiSelect", + "label": "multiSelect", + "type": "boolean" + } + ] + }, + { + "name": "contact", + "label": "contact", + "type": "collection", + "spec": [ + { + "name": "vcard", + "label": "vcard", + "type": "text" + } + ] + }, + { + "name": "requestPhoneNumber", + "label": "requestPhoneNumber", + "type": "collection" + }, + { + "name": "product", + "label": "product", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "currencyCode", + "label": "currencyCode", + "type": "text" + }, + { + "name": "priceAmount1000", + "label": "priceAmount1000", + "type": "number" + }, + { + "name": "salePriceAmount1000", + "label": "salePriceAmount1000", + "type": "number" + }, + { + "name": "retailerId", + "label": "retailerId", + "type": "text" + }, + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "imageCount", + "label": "imageCount", + "type": "number" + }, + { + "name": "image", + "label": "image", + "type": "collection" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + } + ] + }, + { + "name": "productList", + "label": "productList", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "order", + "label": "order", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "thumbnailBase64", + "label": "thumbnailBase64", + "type": "text" + }, + { + "name": "itemCount", + "label": "itemCount", + "type": "number" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "message", + "label": "message", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "sellerId", + "label": "sellerId", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "totalAmount1000", + "label": "totalAmount1000", + "type": "number" + }, + { + "name": "totalCurrencyCode", + "label": "totalCurrencyCode", + "type": "text" + }, + { + "name": "catalogType", + "label": "catalogType", + "type": "text" + } + ] + }, + { + "name": "list", + "label": "list", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "buttons", + "label": "buttons", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "buttons", + "label": "buttons", + "type": "array" + } + ] + }, + { + "name": "addressMessage", + "label": "addressMessage", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "country", + "label": "country", + "type": "text" + } + ] + }, + { + "name": "flow", + "label": "flow", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "action", + "label": "action", + "type": "text" + }, + { + "name": "screen", + "label": "screen", + "type": "text" + }, + { + "name": "dataJson", + "label": "dataJson", + "type": "text" + } + ] + }, + { + "name": "callPermissionRequest", + "label": "callPermissionRequest", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + } + ] + }, + { + "name": "template", + "label": "template", + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "language", + "label": "language", + "type": "text" + }, + { + "name": "components", + "label": "components", + "type": "array" + } + ] + } + ] + }, + { + "name": "mediaId", + "label": "mediaId", + "type": "text" + }, + { + "name": "hdMessageId", + "label": "hdMessageId", + "type": "text" + } + ] + } +] diff --git a/make/modules/sendTemplate/metadata.json b/make/modules/sendTemplate/metadata.json new file mode 100644 index 0000000..f12547f --- /dev/null +++ b/make/modules/sendTemplate/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Send Template", + "description": "Sends a template with its approved name and language.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/sendTemplate/parameters.json b/make/modules/sendTemplate/parameters.json new file mode 100644 index 0000000..58fff62 --- /dev/null +++ b/make/modules/sendTemplate/parameters.json @@ -0,0 +1,72 @@ +[ + { + "name": "session", + "type": "text", + "label": "Number", + "required": true, + "help": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + { + "name": "recipientType", + "type": "select", + "label": "Recipient Type", + "required": true, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + { + "name": "recipient", + "type": "text", + "label": "Recipient", + "required": true, + "help": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + { + "name": "templateName", + "type": "text", + "label": "Template Name", + "required": true + }, + { + "name": "language", + "type": "text", + "label": "Template Language", + "required": true, + "help": "The exact language code for the selected template." + }, + { + "name": "components", + "type": "text", + "label": "Template Components", + "required": false, + "default": "[]", + "help": "JSON array matching the template definition." + }, + { + "name": "idempotencyKey", + "type": "text", + "label": "Idempotency Key", + "required": true, + "help": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + { + "name": "writeCreatedAt", + "type": "text", + "label": "Write Started At", + "required": true, + "help": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +] diff --git a/make/modules/sendText/communication.json b/make/modules/sendText/communication.json new file mode 100644 index 0000000..156174c --- /dev/null +++ b/make/modules/sendText/communication.json @@ -0,0 +1,19 @@ +{ + "url": "{{get(polymorfaRequest('sendText'; parameters; connection); 'url')}}", + "method": "{{get(polymorfaRequest('sendText'; parameters; connection); 'method')}}", + "headers": "{{get(polymorfaRequest('sendText'; parameters; connection); 'headers')}}", + "qs": "{{get(polymorfaRequest('sendText'; parameters; connection); 'query')}}", + "body": "{{get(polymorfaRequest('sendText'; parameters; connection); 'body')}}", + "type": "json", + "followRedirect": false, + "response": { + "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", + "output": "{{polymorfaResult(statusCode; headers; body)}}" + }, + "log": { + "sanitize": [ + "request.headers.authorization", + "request.headers.idempotency-key" + ] + } +} diff --git a/make/modules/sendText/interface.json b/make/modules/sendText/interface.json new file mode 100644 index 0000000..1585677 --- /dev/null +++ b/make/modules/sendText/interface.json @@ -0,0 +1,669 @@ +[ + { + "name": "success", + "label": "success", + "type": "boolean" + }, + { + "name": "data", + "label": "data", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "whatsapp_ids", + "label": "whatsapp_ids", + "type": "collection", + "spec": [ + { + "name": "linked_devices", + "label": "linked_devices", + "type": "text" + }, + { + "name": "official_api", + "label": "official_api", + "type": "text" + } + ] + }, + { + "name": "whatsapp_id", + "label": "whatsapp_id", + "type": "text" + }, + { + "name": "conversation", + "label": "conversation", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "phoneNumber", + "label": "phoneNumber", + "type": "text" + }, + { + "name": "bsuid", + "label": "bsuid", + "type": "text" + }, + { + "name": "username", + "label": "username", + "type": "text" + } + ] + }, + { + "name": "timestamp", + "label": "timestamp", + "type": "date" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "type", + "label": "type", + "type": "text" + }, + { + "name": "content", + "label": "content", + "type": "collection", + "spec": [ + { + "name": "text", + "label": "text", + "type": "text" + }, + { + "name": "image", + "label": "image", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + }, + { + "name": "quality", + "label": "quality", + "type": "text" + } + ] + }, + { + "name": "video", + "label": "video", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "file", + "label": "file", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "filename", + "label": "filename", + "type": "text" + }, + { + "name": "thumbnail", + "label": "thumbnail", + "type": "text" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "voice", + "label": "voice", + "type": "collection", + "spec": [ + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "base64", + "label": "base64", + "type": "text" + }, + { + "name": "mimeType", + "label": "mimeType", + "type": "text" + }, + { + "name": "caption", + "label": "caption", + "type": "text" + }, + { + "name": "ptt", + "label": "ptt", + "type": "boolean" + }, + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "sticker", + "label": "sticker", + "type": "collection", + "spec": [ + { + "name": "descriptor", + "label": "descriptor", + "type": "collection" + } + ] + }, + { + "name": "location", + "label": "location", + "type": "collection", + "spec": [ + { + "name": "lat", + "label": "lat", + "type": "number" + }, + { + "name": "long", + "label": "long", + "type": "number" + }, + { + "name": "address", + "label": "address", + "type": "text" + } + ] + }, + { + "name": "poll", + "label": "poll", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "options", + "label": "options", + "type": "array" + }, + { + "name": "multiSelect", + "label": "multiSelect", + "type": "boolean" + } + ] + }, + { + "name": "contact", + "label": "contact", + "type": "collection", + "spec": [ + { + "name": "vcard", + "label": "vcard", + "type": "text" + } + ] + }, + { + "name": "requestPhoneNumber", + "label": "requestPhoneNumber", + "type": "collection" + }, + { + "name": "product", + "label": "product", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "currencyCode", + "label": "currencyCode", + "type": "text" + }, + { + "name": "priceAmount1000", + "label": "priceAmount1000", + "type": "number" + }, + { + "name": "salePriceAmount1000", + "label": "salePriceAmount1000", + "type": "number" + }, + { + "name": "retailerId", + "label": "retailerId", + "type": "text" + }, + { + "name": "url", + "label": "url", + "type": "text" + }, + { + "name": "imageCount", + "label": "imageCount", + "type": "number" + }, + { + "name": "image", + "label": "image", + "type": "collection" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + } + ] + }, + { + "name": "productList", + "label": "productList", + "type": "collection", + "spec": [ + { + "name": "businessOwnerId", + "label": "businessOwnerId", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "order", + "label": "order", + "type": "collection", + "spec": [ + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "thumbnailBase64", + "label": "thumbnailBase64", + "type": "text" + }, + { + "name": "itemCount", + "label": "itemCount", + "type": "number" + }, + { + "name": "status", + "label": "status", + "type": "text" + }, + { + "name": "message", + "label": "message", + "type": "text" + }, + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "sellerId", + "label": "sellerId", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "totalAmount1000", + "label": "totalAmount1000", + "type": "number" + }, + { + "name": "totalCurrencyCode", + "label": "totalCurrencyCode", + "type": "text" + }, + { + "name": "catalogType", + "label": "catalogType", + "type": "text" + } + ] + }, + { + "name": "list", + "label": "list", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "description", + "label": "description", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "sections", + "label": "sections", + "type": "array" + } + ] + }, + { + "name": "buttons", + "label": "buttons", + "type": "collection", + "spec": [ + { + "name": "title", + "label": "title", + "type": "text" + }, + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "buttons", + "label": "buttons", + "type": "array" + } + ] + }, + { + "name": "addressMessage", + "label": "addressMessage", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "country", + "label": "country", + "type": "text" + } + ] + }, + { + "name": "flow", + "label": "flow", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + }, + { + "name": "buttonText", + "label": "buttonText", + "type": "text" + }, + { + "name": "footer", + "label": "footer", + "type": "text" + }, + { + "name": "id", + "label": "id", + "type": "text" + }, + { + "name": "token", + "label": "token", + "type": "text" + }, + { + "name": "action", + "label": "action", + "type": "text" + }, + { + "name": "screen", + "label": "screen", + "type": "text" + }, + { + "name": "dataJson", + "label": "dataJson", + "type": "text" + } + ] + }, + { + "name": "callPermissionRequest", + "label": "callPermissionRequest", + "type": "collection", + "spec": [ + { + "name": "body", + "label": "body", + "type": "text" + } + ] + }, + { + "name": "template", + "label": "template", + "type": "collection", + "spec": [ + { + "name": "name", + "label": "name", + "type": "text" + }, + { + "name": "language", + "label": "language", + "type": "text" + }, + { + "name": "components", + "label": "components", + "type": "array" + } + ] + } + ] + }, + { + "name": "mediaId", + "label": "mediaId", + "type": "text" + }, + { + "name": "hdMessageId", + "label": "hdMessageId", + "type": "text" + } + ] + } +] diff --git a/make/modules/sendText/metadata.json b/make/modules/sendText/metadata.json new file mode 100644 index 0000000..8c61d8a --- /dev/null +++ b/make/modules/sendText/metadata.json @@ -0,0 +1,6 @@ +{ + "label": "Send Text", + "description": "Sends a text message or replies to a message.", + "connection": "project", + "type": "action" +} diff --git a/make/modules/sendText/parameters.json b/make/modules/sendText/parameters.json new file mode 100644 index 0000000..45a4c4a --- /dev/null +++ b/make/modules/sendText/parameters.json @@ -0,0 +1,64 @@ +[ + { + "name": "session", + "type": "text", + "label": "Number", + "required": true, + "help": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + { + "name": "recipientType", + "type": "select", + "label": "Recipient Type", + "required": true, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + { + "name": "recipient", + "type": "text", + "label": "Recipient", + "required": true, + "help": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + { + "name": "text", + "type": "text", + "label": "Text", + "required": true + }, + { + "name": "quotedMessageId", + "type": "text", + "label": "Reply to Message ID", + "required": false, + "help": "Use the Polymorfa message ID, not a WhatsApp transport reference." + }, + { + "name": "idempotencyKey", + "type": "text", + "label": "Idempotency Key", + "required": true, + "help": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + { + "name": "writeCreatedAt", + "type": "text", + "label": "Write Started At", + "required": true, + "help": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +] diff --git a/make/webhooks/callEnded/attach.json b/make/webhooks/callEnded/attach.json new file mode 100644 index 0000000..8eed788 --- /dev/null +++ b/make/webhooks/callEnded/attach.json @@ -0,0 +1,50 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, + "response": { + "temp": { + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}" + }, + "data": { + "externalHookId": "{{temp.ownedId}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'value')}}" + } + } + }, + { + "condition": "{{!temp.ownedId}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "POST", + "headers": { + "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key')}}" + }, + "body": { + "url": "{{webhook.url}}", + "eventTypes": [ + "call.ended" + ], + "format": "native", + "enabled": true, + "headers": [ + { + "name": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'name')}}", + "value": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'value')}}" + } + ] + }, + "response": { + "data": { + "externalHookId": "{{body.data.webhook.id}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'value')}}" + } + } + } +] diff --git a/make/webhooks/callEnded/communication.json b/make/webhooks/callEnded/communication.json new file mode 100644 index 0000000..eaa0032 --- /dev/null +++ b/make/webhooks/callEnded/communication.json @@ -0,0 +1,15 @@ +{ + "condition": "{{polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'callEnded'; headers; body)}}", + "respond": { + "status": "{{if(polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'callEnded'; headers; body); 200; 401)}}", + "type": "json", + "body": {} + }, + "output": { + "id": "{{body.id}}", + "session": "{{body.session}}", + "timestamp": "{{body.timestamp}}", + "event": "{{body.event}}", + "payload": "{{body.payload}}" + } +} diff --git a/make/webhooks/callEnded/detach.json b/make/webhooks/callEnded/detach.json new file mode 100644 index 0000000..c8b76b5 --- /dev/null +++ b/make/webhooks/callEnded/detach.json @@ -0,0 +1,20 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "GET", + "response": { + "temp": { + "gone": "{{statusCode = 404}}" + }, + "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded'))}}" + } + }, + { + "condition": "{{!temp.gone}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "DELETE", + "headers": { + "Idempotency-Key": "integration-delete-{{webhook.externalHookId}}" + } + } +] diff --git a/make/webhooks/callEnded/metadata.json b/make/webhooks/callEnded/metadata.json new file mode 100644 index 0000000..2e2cecf --- /dev/null +++ b/make/webhooks/callEnded/metadata.json @@ -0,0 +1,5 @@ +{ + "type": "dedicated", + "connection": "project", + "label": "Call Ended" +} diff --git a/make/webhooks/callEnded/parameters.json b/make/webhooks/callEnded/parameters.json new file mode 100644 index 0000000..73eeef1 --- /dev/null +++ b/make/webhooks/callEnded/parameters.json @@ -0,0 +1,9 @@ +[ + { + "name": "subscriptionKey", + "label": "Subscription Key", + "type": "text", + "required": true, + "help": "Unique stable label for this webhook. Events are project-wide." + } +] diff --git a/make/webhooks/messageReceived/attach.json b/make/webhooks/messageReceived/attach.json new file mode 100644 index 0000000..a496ca1 --- /dev/null +++ b/make/webhooks/messageReceived/attach.json @@ -0,0 +1,50 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, + "response": { + "temp": { + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}" + }, + "data": { + "externalHookId": "{{temp.ownedId}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'value')}}" + } + } + }, + { + "condition": "{{!temp.ownedId}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "POST", + "headers": { + "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key')}}" + }, + "body": { + "url": "{{webhook.url}}", + "eventTypes": [ + "message.received" + ], + "format": "native", + "enabled": true, + "headers": [ + { + "name": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'name')}}", + "value": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'value')}}" + } + ] + }, + "response": { + "data": { + "externalHookId": "{{body.data.webhook.id}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'value')}}" + } + } + } +] diff --git a/make/webhooks/messageReceived/communication.json b/make/webhooks/messageReceived/communication.json new file mode 100644 index 0000000..a5b891a --- /dev/null +++ b/make/webhooks/messageReceived/communication.json @@ -0,0 +1,15 @@ +{ + "condition": "{{polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'messageReceived'; headers; body)}}", + "respond": { + "status": "{{if(polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'messageReceived'; headers; body); 200; 401)}}", + "type": "json", + "body": {} + }, + "output": { + "id": "{{body.id}}", + "session": "{{body.session}}", + "timestamp": "{{body.timestamp}}", + "event": "{{body.event}}", + "payload": "{{body.payload}}" + } +} diff --git a/make/webhooks/messageReceived/detach.json b/make/webhooks/messageReceived/detach.json new file mode 100644 index 0000000..e6e7f3a --- /dev/null +++ b/make/webhooks/messageReceived/detach.json @@ -0,0 +1,20 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "GET", + "response": { + "temp": { + "gone": "{{statusCode = 404}}" + }, + "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived'))}}" + } + }, + { + "condition": "{{!temp.gone}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "DELETE", + "headers": { + "Idempotency-Key": "integration-delete-{{webhook.externalHookId}}" + } + } +] diff --git a/make/webhooks/messageReceived/metadata.json b/make/webhooks/messageReceived/metadata.json new file mode 100644 index 0000000..a42b2e5 --- /dev/null +++ b/make/webhooks/messageReceived/metadata.json @@ -0,0 +1,5 @@ +{ + "type": "dedicated", + "connection": "project", + "label": "New Message" +} diff --git a/make/webhooks/messageReceived/parameters.json b/make/webhooks/messageReceived/parameters.json new file mode 100644 index 0000000..73eeef1 --- /dev/null +++ b/make/webhooks/messageReceived/parameters.json @@ -0,0 +1,9 @@ +[ + { + "name": "subscriptionKey", + "label": "Subscription Key", + "type": "text", + "required": true, + "help": "Unique stable label for this webhook. Events are project-wide." + } +] diff --git a/make/webhooks/messageState/attach.json b/make/webhooks/messageState/attach.json new file mode 100644 index 0000000..4678bc8 --- /dev/null +++ b/make/webhooks/messageState/attach.json @@ -0,0 +1,53 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, + "response": { + "temp": { + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}" + }, + "data": { + "externalHookId": "{{temp.ownedId}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'value')}}" + } + } + }, + { + "condition": "{{!temp.ownedId}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "POST", + "headers": { + "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key')}}" + }, + "body": { + "url": "{{webhook.url}}", + "eventTypes": [ + "message.sent", + "message.ack", + "message.failed", + "message.update" + ], + "format": "native", + "enabled": true, + "headers": [ + { + "name": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'name')}}", + "value": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'value')}}" + } + ] + }, + "response": { + "data": { + "externalHookId": "{{body.data.webhook.id}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'value')}}" + } + } + } +] diff --git a/make/webhooks/messageState/communication.json b/make/webhooks/messageState/communication.json new file mode 100644 index 0000000..ce93d70 --- /dev/null +++ b/make/webhooks/messageState/communication.json @@ -0,0 +1,15 @@ +{ + "condition": "{{polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'messageState'; headers; body)}}", + "respond": { + "status": "{{if(polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'messageState'; headers; body); 200; 401)}}", + "type": "json", + "body": {} + }, + "output": { + "id": "{{body.id}}", + "session": "{{body.session}}", + "timestamp": "{{body.timestamp}}", + "event": "{{body.event}}", + "payload": "{{body.payload}}" + } +} diff --git a/make/webhooks/messageState/detach.json b/make/webhooks/messageState/detach.json new file mode 100644 index 0000000..26eb96d --- /dev/null +++ b/make/webhooks/messageState/detach.json @@ -0,0 +1,20 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "GET", + "response": { + "temp": { + "gone": "{{statusCode = 404}}" + }, + "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState'))}}" + } + }, + { + "condition": "{{!temp.gone}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "DELETE", + "headers": { + "Idempotency-Key": "integration-delete-{{webhook.externalHookId}}" + } + } +] diff --git a/make/webhooks/messageState/metadata.json b/make/webhooks/messageState/metadata.json new file mode 100644 index 0000000..90bdcdd --- /dev/null +++ b/make/webhooks/messageState/metadata.json @@ -0,0 +1,5 @@ +{ + "type": "dedicated", + "connection": "project", + "label": "Message State Changed" +} diff --git a/make/webhooks/messageState/parameters.json b/make/webhooks/messageState/parameters.json new file mode 100644 index 0000000..73eeef1 --- /dev/null +++ b/make/webhooks/messageState/parameters.json @@ -0,0 +1,9 @@ +[ + { + "name": "subscriptionKey", + "label": "Subscription Key", + "type": "text", + "required": true, + "help": "Unique stable label for this webhook. Events are project-wide." + } +] diff --git a/make/webhooks/numberState/attach.json b/make/webhooks/numberState/attach.json new file mode 100644 index 0000000..e50ec09 --- /dev/null +++ b/make/webhooks/numberState/attach.json @@ -0,0 +1,52 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, + "response": { + "temp": { + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}" + }, + "data": { + "externalHookId": "{{temp.ownedId}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'value')}}" + } + } + }, + { + "condition": "{{!temp.ownedId}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "POST", + "headers": { + "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key')}}" + }, + "body": { + "url": "{{webhook.url}}", + "eventTypes": [ + "session.status", + "session.connected", + "session.logged_out" + ], + "format": "native", + "enabled": true, + "headers": [ + { + "name": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'name')}}", + "value": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'value')}}" + } + ] + }, + "response": { + "data": { + "externalHookId": "{{body.data.webhook.id}}", + "ownedUrl": "{{webhook.url}}", + "receiverName": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'name')}}", + "receiverValue": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'value')}}" + } + } + } +] diff --git a/make/webhooks/numberState/communication.json b/make/webhooks/numberState/communication.json new file mode 100644 index 0000000..901b226 --- /dev/null +++ b/make/webhooks/numberState/communication.json @@ -0,0 +1,15 @@ +{ + "condition": "{{polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'numberState'; headers; body)}}", + "respond": { + "status": "{{if(polymorfaReceive(parameters.receiverName; parameters.receiverValue; 'numberState'; headers; body); 200; 401)}}", + "type": "json", + "body": {} + }, + "output": { + "id": "{{body.id}}", + "session": "{{body.session}}", + "timestamp": "{{body.timestamp}}", + "event": "{{body.event}}", + "payload": "{{body.payload}}" + } +} diff --git a/make/webhooks/numberState/detach.json b/make/webhooks/numberState/detach.json new file mode 100644 index 0000000..167b5fc --- /dev/null +++ b/make/webhooks/numberState/detach.json @@ -0,0 +1,20 @@ +[ + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "GET", + "response": { + "temp": { + "gone": "{{statusCode = 404}}" + }, + "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState'))}}" + } + }, + { + "condition": "{{!temp.gone}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "method": "DELETE", + "headers": { + "Idempotency-Key": "integration-delete-{{webhook.externalHookId}}" + } + } +] diff --git a/make/webhooks/numberState/metadata.json b/make/webhooks/numberState/metadata.json new file mode 100644 index 0000000..85a747f --- /dev/null +++ b/make/webhooks/numberState/metadata.json @@ -0,0 +1,5 @@ +{ + "type": "dedicated", + "connection": "project", + "label": "Number State Changed" +} diff --git a/make/webhooks/numberState/parameters.json b/make/webhooks/numberState/parameters.json new file mode 100644 index 0000000..73eeef1 --- /dev/null +++ b/make/webhooks/numberState/parameters.json @@ -0,0 +1,9 @@ +[ + { + "name": "subscriptionKey", + "label": "Subscription Key", + "type": "text", + "required": true, + "help": "Unique stable label for this webhook. Events are project-wide." + } +] diff --git a/n8n/LICENSE b/n8n/LICENSE new file mode 100644 index 0000000..160e2eb --- /dev/null +++ b/n8n/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Polymorfa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/n8n/NOTICE.md b/n8n/NOTICE.md new file mode 100644 index 0000000..9f55905 --- /dev/null +++ b/n8n/NOTICE.md @@ -0,0 +1,5 @@ +The Polymorfa marks identify Polymorfa's service. The n8n icon uses the approved +Polymorfa website favicon. Trademark rights are separate from the MIT code license. + +n8n, Make, Zapier and Pipedream are names of their respective providers. This +development repository does not claim provider certification or approval. diff --git a/n8n/README.md b/n8n/README.md new file mode 100644 index 0000000..b3e1bd3 --- /dev/null +++ b/n8n/README.md @@ -0,0 +1,40 @@ +# Polymorfa for n8n + +Development source for the Polymorfa action and trigger nodes. This package has +not been published or verified by n8n. Do not treat an npm name or this source as +a marketplace listing. + +The node sends text/replies, templates, image/video/file/voice media and reactions; +lists Numbers/templates; and makes allowlisted public API calls. It is usable as +an AI tool. The trigger receives project-wide message, Number-state and completed +call events. Functions and asynchronous operation reads are not exposed yet. + +Build from this repository's root using the instructions in `MAINTAINING.md`. +Then pack the workspace for a controlled development installation: + +```sh +npm pack --workspace n8n-nodes-polymorfa +``` + +Create a **Polymorfa API** credential with a restricted project token and project +UUID. The credential test and Number picker need `sessions:read`; sends and +reactions need `messages:write`; template lookup needs `templates:read`. +Triggers also need `webhooks:read` and `webhooks:manage`. + +For every write, map a stable **Idempotency Key** and **Write Started At** ISO +timestamp from the saved business event. Preserve both during retries. The +adapter refuses writes older than 23 hours. Recover the first result or +reconcile the effect before starting a different write. `idempotency_completed` +does not contain the original message ID. + +Media accepts HTTPS URLs, base64 or a named n8n binary input field. The connector +caps decoded bytes at 4 MiB. Provider/API bounds can be lower. IDs remain strings. + +Give each trigger a unique, stable **Subscription Key**. Webhook subscriptions +are project-wide. Filtering a workflow after receipt does not prevent disclosure +to n8n. Dedupe by event `id` before downstream effects; repeated delivery is +possible. Delete/disable the owned webhook before revoking its credential. + +n8n executes your workflow under its own hosting and retention settings. Choosing +a Polymorfa EU or Brazil region does not place n8n's processing in that region. +Test with synthetic data and check the platform's logs before release. diff --git a/n8n/credentials/PolymorfaApi.credentials.ts b/n8n/credentials/PolymorfaApi.credentials.ts new file mode 100644 index 0000000..1d9de7d --- /dev/null +++ b/n8n/credentials/PolymorfaApi.credentials.ts @@ -0,0 +1,14 @@ +import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow'; + +export class PolymorfaApi implements ICredentialType { + name = 'polymorfaApi'; + displayName = 'Polymorfa API'; + documentationUrl = 'https://docs.polymorfa.com'; + icon: ICredentialType['icon'] = { light: 'file:../nodes/Polymorfa/polymorfa.light.svg', dark: 'file:../nodes/Polymorfa/polymorfa.dark.svg' }; + properties: INodeProperties[] = [ + { displayName: 'Project Token', name: 'apiKey', type: 'string', typeOptions: { password: true }, default: '', required: true }, + { displayName: 'Project ID', name: 'projectId', type: 'string', default: '', required: true, description: 'The project UUID for the token. The token needs sessions:read to test the connection.' }, + ]; + authenticate: IAuthenticateGeneric = { type: 'generic', properties: { headers: { Authorization: '=Bearer {{$credentials.apiKey}}', 'Polymorfa-Version': '2026-09-22' } } }; + test: ICredentialTestRequest = { request: { baseURL: 'https://api.polymorfa.com', url: '/platform/sessions', qs: { projectId: '={{$credentials.projectId}}' } } }; +} diff --git a/n8n/eslint.config.mjs b/n8n/eslint.config.mjs new file mode 100644 index 0000000..bab3b8a --- /dev/null +++ b/n8n/eslint.config.mjs @@ -0,0 +1,2 @@ +import { config } from '@n8n/node-cli/eslint'; +export default config; diff --git a/n8n/nodes/Polymorfa/Polymorfa.node.ts b/n8n/nodes/Polymorfa/Polymorfa.node.ts new file mode 100644 index 0000000..ed8d30d --- /dev/null +++ b/n8n/nodes/Polymorfa/Polymorfa.node.ts @@ -0,0 +1,60 @@ +import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; +import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeProperties, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { actions, fields } from '../../shared/catalog'; +import { execute, MAX_MEDIA_BYTES } from '../../shared/core'; +import { transport } from './transport'; + +type Field = { label: string; required?: boolean; help?: string; options?: Record; default?: string; json?: boolean }; +const descriptors = fields as Record; +const properties: INodeProperties[] = [ + { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, default: 'sendText', options: Object.entries(actions).map(([value, a]) => ({ name: a.label, value, description: a.description, action: a.label })) }, + ...Object.entries(descriptors).map(([name, f]): INodeProperties => ({ + displayName: f.label, name, type: f.options || name === 'session' ? 'options' : 'string', default: f.default ?? '', + ...(name === 'session' ? { typeOptions: { loadOptionsMethod: 'getNumbers' } } : {}), + required: f.required ?? false, description: f.help, + ...(f.options ? { options: Object.entries({ ...f.options, ...(name === 'mediaSource' ? { binary: 'n8n Binary Input' } : {}) }).map(([value, label]) => ({ name: label, value })) } : {}), + displayOptions: { show: { operation: Object.entries(actions).filter(([, a]) => (a.fields as readonly string[]).includes(name)).map(([key]) => key) } }, + })), + { displayName: 'Binary Input Field', name: 'binaryProperty', type: 'string', default: 'data', required: true, displayOptions: { show: { operation: ['sendMedia'], mediaSource: ['binary'] } } }, +]; +// A binary input supplies media bytes directly; it does not require a second URL field. +const mediaField = properties.find(p => p.name === 'media'); +if (mediaField) mediaField.displayOptions = { show: { operation: ['sendMedia'], mediaSource: ['url', 'base64'] } }; + +export class Polymorfa implements INodeType { + description: INodeTypeDescription = { + displayName: 'Polymorfa', name: 'polymorfa', icon: { light: 'file:polymorfa.light.svg', dark: 'file:polymorfa.dark.svg' }, group: ['transform'], version: 1, + subtitle: '={{$parameter["operation"]}}', description: 'Send messages and use the Polymorfa API', defaults: { name: 'Polymorfa' }, + inputs: [NodeConnectionTypes.Main], outputs: [NodeConnectionTypes.Main], usableAsTool: true, + credentials: [{ name: 'polymorfaApi', required: true }], properties, + }; + methods = { loadOptions: { + async getNumbers(this: ILoadOptionsFunctions) { + const auth = await this.getCredentials('polymorfaApi'); + const result = await execute('listNumbers', {}, auth, transport(this), 'n8n'); + return result.data.map((n: { sessionId: string; name?: string | null }) => ({ name: n.name ? `${n.name} (${n.sessionId})` : n.sessionId, value: n.sessionId })); + }, + } }; + async execute(this: IExecuteFunctions): Promise { + const auth = await this.getCredentials('polymorfaApi'); const result: INodeExecutionData[] = []; + for (let i = 0; i < this.getInputData().length; i++) { + try { + const action = this.getNodeParameter('operation', i) as keyof typeof actions; + const p: Record = {}; + for (const name of actions[action].fields) p[name] = this.getNodeParameter(name, i, ''); + if (action === 'sendMedia' && p.mediaSource === 'binary') { + const property = this.getNodeParameter('binaryProperty', i) as string; + const bytes = await this.helpers.getBinaryDataBuffer(i, property); + if (bytes.length > MAX_MEDIA_BYTES) throw new NodeOperationError(this.getNode(), 'Media exceeds the 4 MiB connector limit.', { itemIndex: i }); + p.mediaSource = 'base64'; p.media = bytes.toString('base64'); + p.mimeType ||= this.getInputData()[i].binary?.[property]?.mimeType; + } + const data = await execute(action, p, auth, transport(this), 'n8n'); + result.push({ json: data, pairedItem: { item: i } }); + } catch (error) { + throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i }); + } + } + return [result]; + } +} diff --git a/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts new file mode 100644 index 0000000..c53ab00 --- /dev/null +++ b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts @@ -0,0 +1,54 @@ +import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; +import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow'; +import { events } from '../../shared/catalog'; +import { subscribe, unsubscribe, authenticate, envelope, find, identity } from '../../shared/hooks'; +import { transport } from './transport'; + +export class PolymorfaTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Polymorfa Trigger', name: 'polymorfaTrigger', icon: { light: 'file:polymorfa.light.svg', dark: 'file:polymorfa.dark.svg' }, group: ['trigger'], version: 1, + subtitle: '={{$parameter["eventGroup"]}}', + description: 'Receive authenticated project events from Polymorfa', defaults: { name: 'Polymorfa Trigger' }, + inputs: [], outputs: [NodeConnectionTypes.Main], credentials: [{ name: 'polymorfaApi', required: true }], + webhooks: [{ name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook' }], + properties: [ + { displayName: 'Event', name: 'eventGroup', type: 'options', default: 'messageReceived', options: Object.entries(events).map(([value, event]) => ({ name: event.label, value, description: event.description })) }, + { displayName: 'Subscription Key', name: 'subscriptionKey', type: 'string', required: true, default: '', description: 'A unique stable label for this trigger. Keep it on reactivation; use a different value for another trigger.' }, + { displayName: 'Events are project-wide. Filtering after receipt does not limit which Numbers disclose events to n8n.', name: 'scopeNotice', type: 'notice', default: '' }, + ], + }; + webhookMethods = { default: { + async checkExists(this: IHookFunctions) { + const auth = await this.getCredentials('polymorfaApi'); + const url = this.getNodeWebhookUrl('default')!, group = this.getNodeParameter('eventGroup') as string, subscriptionKey = this.getNodeParameter('subscriptionKey') as string; + const found = await find(auth, url, group, subscriptionKey, transport(this), 'n8n'); + if (found) this.getWorkflowStaticData('node').subscription = { id: found.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }; + return !!found; + }, + async create(this: IHookFunctions) { + const auth = await this.getCredentials('polymorfaApi'); + const hook = await subscribe(auth, this.getNodeWebhookUrl('default')!, this.getNodeParameter('eventGroup') as string, this.getNodeParameter('subscriptionKey') as string, transport(this), 'n8n'); + this.getWorkflowStaticData('node').subscription = hook; return true; + }, + async delete(this: IHookFunctions) { + const data = this.getWorkflowStaticData('node'); + const owned = data.subscription as { id: string; url: string; group: string; subscriptionKey: string; markerName: string } | undefined; + if (owned) { + await unsubscribe(await this.getCredentials('polymorfaApi'), owned.id, owned.url, owned.group, owned.subscriptionKey, transport(this), 'n8n', owned.markerName); + delete data.subscription; + } + return true; + }, + } }; + async webhook(this: IWebhookFunctions): Promise { + const group = this.getNodeParameter('eventGroup') as string; + const auth = await this.getCredentials('polymorfaApi'); + if (!authenticate(auth, group, this.getNodeParameter('subscriptionKey') as string, this.getHeaderData())) { + this.getResponseObject().status(401).end(); return { noWebhookResponse: true }; + } + try { + const event = envelope(this.getBodyData(), group); + return { workflowData: [this.helpers.returnJsonArray(event)] }; + } catch (error) { throw new NodeOperationError(this.getNode(), error as Error); } + } +} diff --git a/n8n/nodes/Polymorfa/polymorfa.dark.svg b/n8n/nodes/Polymorfa/polymorfa.dark.svg new file mode 100644 index 0000000..b951ebf --- /dev/null +++ b/n8n/nodes/Polymorfa/polymorfa.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/n8n/nodes/Polymorfa/polymorfa.light.svg b/n8n/nodes/Polymorfa/polymorfa.light.svg new file mode 100644 index 0000000..b951ebf --- /dev/null +++ b/n8n/nodes/Polymorfa/polymorfa.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/n8n/nodes/Polymorfa/transport.ts b/n8n/nodes/Polymorfa/transport.ts new file mode 100644 index 0000000..c2ff18c --- /dev/null +++ b/n8n/nodes/Polymorfa/transport.ts @@ -0,0 +1,12 @@ +import type { IExecuteFunctions, IHookFunctions, IWebhookFunctions, ILoadOptionsFunctions, IHttpRequestOptions } from 'n8n-workflow'; + +export function transport(context: IExecuteFunctions | IHookFunctions | IWebhookFunctions | ILoadOptionsFunctions) { + return async (r: { method: string; url: string; headers: Record; query?: Record; body?: unknown }) => { + const response = await context.helpers.httpRequest({ + method: r.method as IHttpRequestOptions['method'], url: r.url, headers: r.headers, + qs: r.query as IHttpRequestOptions['qs'], body: r.body as IHttpRequestOptions['body'], json: true, + returnFullResponse: true, ignoreHttpStatusErrors: true, disableFollowRedirect: true, timeout: 25000, + }); + return { status: response.statusCode, headers: response.headers, body: response.body }; + }; +} diff --git a/n8n/package.json b/n8n/package.json new file mode 100644 index 0000000..7461d7b --- /dev/null +++ b/n8n/package.json @@ -0,0 +1,21 @@ +{ + "name": "n8n-nodes-polymorfa", + "version": "0.1.0", + "description": "Polymorfa actions and authenticated project webhook triggers for n8n", + "license": "MIT", + "author": { "name": "Polymorfa", "email": "support@polymorfa.com", "url": "https://www.polymorfa.com" }, + "repository": { "type": "git", "url": "git+https://github.com/polymorfa/integrations.git", "directory": "n8n" }, + "homepage": "https://docs.polymorfa.com", + "keywords": ["n8n-community-node-package", "polymorfa", "whatsapp"], + "main": "dist/nodes/Polymorfa/Polymorfa.node.js", + "files": ["dist", "README.md", "LICENSE", "NOTICE.md"], + "scripts": { "build": "n8n-node build", "lint": "n8n-node lint" }, + "n8n": { + "n8nNodesApiVersion": 1, + "credentials": ["dist/credentials/PolymorfaApi.credentials.js"], + "nodes": ["dist/nodes/Polymorfa/Polymorfa.node.js", "dist/nodes/Polymorfa/PolymorfaTrigger.node.js"] + }, + "peerDependencies": { "n8n-workflow": "*" }, + "devDependencies": { "n8n-workflow": "2.16.0" }, + "publishConfig": { "access": "public", "provenance": true } +} diff --git a/n8n/shared/catalog.js b/n8n/shared/catalog.js new file mode 100644 index 0000000..e7ed5c9 --- /dev/null +++ b/n8n/shared/catalog.js @@ -0,0 +1,43 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const actions = { + sendText: { label: 'Send Text', description: 'Sends a text message or replies to a message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'text', 'quotedMessageId', 'idempotencyKey', 'writeCreatedAt'] }, + sendTemplate: { label: 'Send Template', description: 'Sends a template with its approved name and language.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'templateName', 'language', 'components', 'idempotencyKey', 'writeCreatedAt'] }, + sendMedia: { label: 'Send Media', description: 'Sends an image, video, file or voice message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'mediaType', 'mediaSource', 'media', 'mimeType', 'caption', 'filename', 'idempotencyKey', 'writeCreatedAt'] }, + react: { label: 'React to Message', description: 'Adds or removes a reaction on a message.', operation: 'sendReaction', fields: ['session', 'recipientType', 'recipient', 'messageId', 'reaction', 'idempotencyKey', 'writeCreatedAt'] }, + listNumbers: { label: 'List Numbers', description: 'Lists Numbers visible to the connected project.', operation: 'listPlatformSessions', fields: [] }, + listTemplates: { label: 'List Templates', description: 'Lists message templates for a project.', operation: 'listProjectTemplates', fields: ['projectSlug'] }, + apiRequest: { label: 'Make an API Call', description: 'Performs an authorized call to a supported public Polymorfa operation.', operation: null, fields: ['method', 'path', 'requestBody', 'idempotencyKey', 'writeCreatedAt'] }, +}; +const fields = { + session: { label: 'Number', required: true, help: 'The exact Number identifier from Polymorfa. Keep it as a string.' }, + recipientType: { label: 'Recipient Type', required: true, options: { phoneNumber: 'Phone Number', id: 'Conversation ID', bsuid: 'Business-scoped User ID' }, default: 'phoneNumber' }, + recipient: { label: 'Recipient', required: true, help: 'Use an international phone number including +, a conversation ID, or a business-scoped user ID.' }, + text: { label: 'Text', required: true }, + quotedMessageId: { label: 'Reply to Message ID', help: 'Use the Polymorfa message ID, not a WhatsApp transport reference.' }, + templateName: { label: 'Template Name', required: true }, + language: { label: 'Template Language', required: true, help: 'The exact language code for the selected template.' }, + components: { label: 'Template Components', json: true, default: '[]', help: 'JSON array matching the template definition.' }, + mediaType: { label: 'Media Type', required: true, options: { image: 'Image', video: 'Video', file: 'File', voice: 'Voice' }, default: 'image' }, + mediaSource: { label: 'Media Source', required: true, options: { url: 'HTTPS URL', base64: 'Base64 Bytes' }, default: 'url' }, + media: { label: 'Media', required: true, help: 'HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower.' }, + mimeType: { label: 'MIME Type' }, + caption: { label: 'Caption' }, + filename: { label: 'Filename', help: 'Used for file messages.' }, + messageId: { label: 'Message ID', required: true }, + reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, + writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, + projectSlug: { label: 'Project Slug', required: true }, + method: { label: 'Method', required: true, options: { GET: 'GET', POST: 'POST' }, default: 'GET' }, + path: { label: 'Path', required: true, help: 'A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused.' }, + requestBody: { label: 'Request Body', json: true, default: '{}', help: 'JSON object. Authentication and version headers are supplied by the connection.' }, +}; +const events = { + messageReceived: { label: 'New Message', description: 'Triggers when a message is received.', types: ['message.received'] }, + messageState: { label: 'Message State Changed', description: 'Triggers when a message is sent, acknowledged, failed or updated.', types: ['message.sent', 'message.ack', 'message.failed', 'message.update'] }, + numberState: { label: 'Number State Changed', description: 'Triggers when a Number changes state, connects or logs out.', types: ['session.status', 'session.connected', 'session.logged_out'] }, + callEnded: { label: 'Call Ended', description: 'Triggers when a call ends.', types: ['call.ended'] }, +}; +module.exports = { actions, fields, events }; diff --git a/n8n/shared/core.js b/n8n/shared/core.js new file mode 100644 index 0000000..3df9eb8 --- /dev/null +++ b/n8n/shared/core.js @@ -0,0 +1,129 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const ORIGIN = 'https://api.polymorfa.com'; +const VERSION = '2026-09-22'; +const MAX_MEDIA_BYTES = 4 * 1024 * 1024; +const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +function object(value, name) { + let parsed = value; + try { if (typeof value === 'string') parsed = JSON.parse(value); } catch { throw new IntegrationError(`invalid_${name}_json`); } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new IntegrationError(`invalid_${name}`); + return parsed; +} +function conversation(p) { + if (!['phoneNumber', 'id', 'bsuid'].includes(p.recipientType)) throw new IntegrationError('invalid_recipient_type'); + required(p.recipient, 'recipient'); + if (p.recipientType === 'phoneNumber' && !/^\+[1-9]\d{1,14}$/.test(p.recipient)) throw new IntegrationError('invalid_phone_number'); + if (p.recipientType === 'id' && !/^[1-9]\d{0,18}$/.test(p.recipient)) throw new IntegrationError('invalid_conversation_id'); + if (p.recipientType === 'bsuid' && !/^[A-Z]{2}\.[A-Za-z0-9]{1,128}$/.test(p.recipient)) throw new IntegrationError('invalid_bsuid'); + return { [p.recipientType]: p.recipient }; +} +function messageBody(action, p) { + const body = { conversation: conversation(p) }; + if (action === 'sendText') { + body.content = { text: required(p.text, 'text') }; + if (p.quotedMessageId) { + if (!/^[1-9]\d{0,18}$/.test(required(p.quotedMessageId, 'quoted_message_id'))) throw new IntegrationError('invalid_quoted_message_id'); + body.quotedMessage = { id: p.quotedMessageId }; + } + } else if (action === 'sendTemplate') { + let components; + try { components = typeof p.components === 'string' ? JSON.parse(p.components || '[]') : (p.components ?? []); } catch { throw new IntegrationError('invalid_components_json'); } + if (!Array.isArray(components) || components.length > 1000) throw new IntegrationError('invalid_components'); + body.content = { template: { name: required(p.templateName, 'template_name'), language: required(p.language, 'language'), components } }; + if (p.language.length < 2) throw new IntegrationError('invalid_language'); + } else if (action === 'sendMedia') { + if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); + required(p.media, 'media'); + if (p.mediaSource === 'url') { + let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } + if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + throw new IntegrationError('invalid_or_oversized_media'); + } + const media = { [p.mediaSource]: p.media }; + for (const key of ['mimeType', 'caption']) if (p[key]) media[key] = required(p[key], key); + if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); + body.content = { [p.mediaType]: media }; + } else if (action === 'react') { + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = p.reaction; + } else throw new IntegrationError('unknown_action'); + return body; +} +function writeHeaders(p, now = Date.now()) { + const key = required(p.idempotencyKey, 'idempotency_key'); + if (Buffer.byteLength(key, 'utf8') > 255 || /[\x00-\x1f\x7f]/.test(key)) throw new IntegrationError('invalid_idempotency_key'); + const created = Date.parse(required(p.writeCreatedAt, 'write_started_at')); + if (!Number.isFinite(created) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(p.writeCreatedAt) || created > now + 60000 || now - created >= MAX_WRITE_AGE) throw new IntegrationError('write_requires_reconciliation'); + return { 'Idempotency-Key': key }; +} +function headers(auth, provider) { + const token = required(auth.apiKey, 'project_token'); + if (/[\r\n]/.test(token)) throw new IntegrationError('invalid_project_token'); + segment(auth.projectId, 'project_id'); + return { Authorization: `Bearer ${token}`, 'Polymorfa-Version': VERSION, 'Content-Type': 'application/json', 'User-Agent': `polymorfa-${provider}/0.1.0` }; +} +function advanced(p, auth) { + const path = required(p.path, 'path'); + if (!path.startsWith('/') || path.startsWith('//') || /[%\\?#\s]/.test(path) || path.split('/').some(x => x === '.' || x === '..')) throw new IntegrationError('unsupported_path'); + const method = p.method; + const allowed = (method === 'GET' && (path === '/platform/sessions' || /^\/messaging\/projects\/[A-Za-z0-9_-]+\/templates(?:\/[A-Za-z0-9_-]+)?$/.test(path))) || + (method === 'POST' && /^\/messaging\/[A-Za-z0-9_-]+\/messages\/(send|react)$/.test(path)); + if (!allowed) throw new IntegrationError('unsupported_operation'); + const body = method === 'POST' ? object(p.requestBody, 'request_body') : undefined; + if (body) { + const keys = path.endsWith('/react') ? ['conversation', 'id', 'reaction'] : ['conversation', 'isForwarded', 'mentions', 'quotedMessage', 'content']; + if (Object.keys(body).some(k => !keys.includes(k))) throw new IntegrationError('unsupported_request_field'); + } + return { method, url: ORIGIN + path, ...(body ? { body } : {}), ...(path === '/platform/sessions' ? { query: { projectId: auth.projectId } } : {}) }; +} +function buildRequest(action, p, auth, provider = 'integration', now) { + let r; + if (['sendText', 'sendTemplate', 'sendMedia', 'react'].includes(action)) r = { method: 'POST', url: `${ORIGIN}/messaging/${segment(p.session, 'number')}/messages/${action === 'react' ? 'react' : 'send'}`, body: messageBody(action, p) }; + else if (action === 'listNumbers') r = { method: 'GET', url: ORIGIN + '/platform/sessions', query: { projectId: auth.projectId } }; + else if (action === 'listTemplates') r = { method: 'GET', url: `${ORIGIN}/messaging/projects/${segment(p.projectSlug, 'project_slug')}/templates` }; + else if (action === 'apiRequest') r = advanced(p, auth); + else throw new IntegrationError('unknown_action'); + r.headers = { ...headers(auth, provider), ...(r.method === 'POST' ? writeHeaders(p, now) : {}) }; + return r; +} +function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; } +function parseResponse(response) { + const status = response.status; + let body = response.body; + try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + if (status < 200 || status >= 300) { + const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); + const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); + const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + } + if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); + return body; +} +async function execute(action, p, auth, transport, provider, now) { + const request = buildRequest(action, p, auth, provider, now); + let response; + try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } + return parseResponse(response); +} +module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/n8n/shared/hooks.js b/n8n/shared/hooks.js new file mode 100644 index 0000000..38794b9 --- /dev/null +++ b/n8n/shared/hooks.js @@ -0,0 +1,92 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const { createHmac, timingSafeEqual } = require('node:crypto'); +const { events } = require('./catalog.js'); +const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.js'); +function identity(auth, group, subscriptionKey) { + if (!events[group]) throw new IntegrationError('invalid_event_group'); + required(subscriptionKey, 'subscription_key'); + if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); + const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); + const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; +} +function callback(url) { + let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } + if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); + return url; +} +function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { + const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); + const types = events[group].types; + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && + hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); +} +async function call(auth, transport, provider, method, path, body, key, query) { + let response; + try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } + catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } + return parseResponse(response); +} +async function find(auth, url, group, subscriptionKey, transport, provider) { + let cursor; const seen = new Set(); const matches = []; + for (let i = 0; i < 100; i++) { + const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); + if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); + const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); + if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + for (const hook of r.data) { + const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); + if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); + } + if (!r.page?.hasMore) { + if (matches.length > 1) throw new IntegrationError('duplicate_owned_subscriptions'); + return matches[0]; + } + if (typeof r.page.nextCursor !== 'string' || seen.has(r.page.nextCursor)) throw new IntegrationError('invalid_subscription_pagination'); + cursor = r.page.nextCursor; seen.add(cursor); + } + throw new IntegrationError('subscription_inventory_requires_reconciliation'); +} +async function subscribe(auth, url, group, subscriptionKey, transport, provider) { + callback(url); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const existing = await find(auth, url, group, subscriptionKey, transport, provider); + if (existing) return record(existing); + const marker = identity(auth, group, subscriptionKey); + // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + try { + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + return record(r.data.webhook); + } catch { + // A lost response may follow a successful create. Reconcile; never blindly create again. + const recovered = await find(auth, url, group, subscriptionKey, transport, provider); + if (recovered) return record(recovered); + throw new IntegrationError('subscription_requires_reconciliation'); + } +} +async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { + const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; + let hook; + try { hook = (await call(auth, transport, provider, 'GET', path)).data; } + catch (error) { if (error.status === 404) return; throw error; } + // Disabled hooks remain owned and may be deleted; all other ownership checks still apply. + if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); + await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); +} +function authenticate(auth, group, subscriptionKey, incomingHeaders) { + const expected = identity(auth, group, subscriptionKey); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; + return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); +} +function envelope(body, group) { + if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); + // Copy the defined envelope only. Never export arbitrary top-level transport fields. + return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; +} +module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/n8n/shared/samples.json b/n8n/shared/samples.json new file mode 100644 index 0000000..a5b25e2 --- /dev/null +++ b/n8n/shared/samples.json @@ -0,0 +1,65 @@ +{ + "messageReceived": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.received", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "fromMe": false, + "timestamp": 1793534400, + "pushName": "Synthetic contact", + "isGroup": false, + "type": "text", + "text": "Synthetic setup sample. No customer message was read." + } + }, + "messageState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a72", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.sent", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "type": "text", + "timestamp": 1793534400 + } + }, + "numberState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a73", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "session.status", + "payload": { + "status": "connected" + } + }, + "callEnded": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a74", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "call.ended", + "payload": { + "from": { + "id": "739182640518205" + }, + "callId": "SYNTHETIC-CALL", + "durationSeconds": 60, + "reason": "ended", + "direction": "inbound", + "hadVideo": false + } + } +} diff --git a/n8n/tsconfig.json b/n8n/tsconfig.json new file mode 100644 index 0000000..a50cca9 --- /dev/null +++ b/n8n/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2022", "module": "commonjs", "moduleResolution": "node", + "lib": ["ES2022"], "outDir": "dist", "rootDir": ".", + "strict": true, "allowJs": true, "checkJs": false, + "skipLibCheck": true, "esModuleInterop": true, + "resolveJsonModule": true, "declaration": true + }, + "include": ["credentials/**/*.ts", "nodes/**/*.ts", "shared/**/*.js"] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..86a75e2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,20169 @@ +{ + "name": "polymorfa-integrations", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "polymorfa-integrations", + "version": "0.1.0", + "license": "MIT", + "workspaces": [ + "n8n", + "zapier" + ], + "devDependencies": { + "@n8n/node-cli": "0.54.6", + "@pipedream/platform": "3.4.0", + "@types/node": "22.19.1", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "typescript": "5.9.3", + "zapier-platform-cli": "19.3.0" + }, + "engines": { + "node": ">=22 <25" + } + }, + "n8n": { + "name": "n8n-nodes-polymorfa", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "n8n-workflow": "2.16.0" + }, + "peerDependencies": { + "n8n-workflow": "*" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", + "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@asteasolutions/zod-to-openapi": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-7.3.4.tgz", + "integrity": "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "openapi3-ts": "^4.1.2" + }, + "peerDependencies": { + "zod": "^3.20.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.12.tgz", + "integrity": "sha512-2W76mhjrpQFO1lK5e8IvGYB2D0YZ7S2R1Z2BjO2ZUYuW+amcpK/z60U6ZZmzskwoa7sJ6XohP/Wg8H5KxXI/IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.12", + "@babel/types": "^7.29.11", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.12.tgz", + "integrity": "sha512-ACxGoWMV866q/QlZHeCOaQZAR5QclKsIvkAmlDemqjm6+6Bynijs1mH068BzH2N2e+wSuDmBWY87fqKfvhi1Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.12", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz", + "integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.12", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.12.tgz", + "integrity": "sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.11" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", + "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", + "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-flow": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", + "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.29.7.tgz", + "integrity": "sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-flow-strip-types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz", + "integrity": "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.12.tgz", + "integrity": "sha512-MscXe/NAZcOkYEsRajHZFt3ynsb22PUi6CTrp5U7+RYycR9By84mn9mFN54YgfITiqK4hFx3Ju3tZSyzfpS5ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.12", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.12", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.3.tgz", + "integrity": "sha512-49CycTz1aAZmvFcwkeFyhud1HUmRIB5Tabf7ALV+VA4Iu85MTfz2cUwzPagMFAdVBvrWdkCxG67I30tUwwpKsw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@browserbasehq/sdk": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.24.0.tgz", + "integrity": "sha512-v8yTJNUmdn2i4dWqu70EmIbpLChLexduul1SRMO+t+0U6NKA9nLNCynFP74y+r5lKFUpx6JLQkCjqeDOC1PpcA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@browserbasehq/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@browserbasehq/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@browserbasehq/stagehand": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/stagehand/-/stagehand-1.14.0.tgz", + "integrity": "sha512-Hi/EzgMFWz+FKyepxHTrqfTPjpsuBS4zRy3e9sbMpBgLPv+9c0R+YZEvS7Bw4mTS66QtvvURRT6zgDGFotthVQ==", + "deprecated": "This version is no longer maintained. Please upgrade to the latest version of @browserbasehq/stagehand.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "@browserbasehq/sdk": "^2.0.0", + "ws": "^8.18.0", + "zod-to-json-schema": "^3.23.5" + }, + "peerDependencies": { + "@playwright/test": "^1.42.1", + "deepmerge": "^4.3.1", + "dotenv": "^16.4.5", + "openai": "^4.62.1", + "zod": "^3.23.8" + } + }, + "node_modules/@browserbasehq/stagehand/node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.4.tgz", + "integrity": "sha512-20rPE8EX0dSg6JGOVe31HpqsLYZTDSlcFJZA5mpZqQxcZ71vh/SXCMX+UuHO1mMcZBDKeb752z+3L8IWa+0Svg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@clack/core": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", + "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", + "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "0.5.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.9.tgz", + "integrity": "sha512-R6siwR65Hm+3yfgP7o8DKhNvputQAwfoz9zTc3kyDudnomj2/BcLmD+uGQQPICjuFUp8ounPBU+jmKsocwVVAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@ibm-cloud/watsonx-ai": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.7.17.tgz", + "integrity": "sha512-ebmYry7sXLbxN1igjmqLEzr5sG0W5y0bXXqyVpbOaAyCA9+rcCAiC+l1xwcS/csY/d0JmLOwIPLiJe4SKx5UxA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "form-data": "^4.0.4", + "ibm-cloud-sdk-core": "^5.4.20" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@jitl/quickjs-ffi-types": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.32.0.tgz", + "integrity": "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jitl/quickjs-wasmfile-debug-asyncify": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.32.0.tgz", + "integrity": "sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.32.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-debug-sync": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.32.0.tgz", + "integrity": "sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.32.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-release-asyncify": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.32.0.tgz", + "integrity": "sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.32.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-release-sync": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.32.0.tgz", + "integrity": "sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.32.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@langchain/classic": { + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@langchain/classic/-/classic-1.0.27.tgz", + "integrity": "sha512-dMq8rgt3cy/QoHrB6HIKobievyW00DfuZcASG0aMy47Pf5TQq3vZM2tLT1kkCBe2yJnncQwqmAzxoEgFLJZrcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@langchain/openai": "1.4.1", + "@langchain/textsplitters": "1.0.1", + "handlebars": "^4.7.9", + "js-yaml": "^4.1.1", + "jsonpointer": "^5.0.1", + "openapi-types": "^12.1.3", + "uuid": "^10.0.0", + "yaml": "^2.8.3", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "langsmith": ">=0.4.0 <1.0.0" + }, + "peerDependencies": { + "@langchain/core": "^1.0.0", + "cheerio": "*", + "peggy": "^5.1.0", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "cheerio": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/@langchain/classic/node_modules/@langchain/openai": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.4.1.tgz", + "integrity": "sha512-jaHk4TnLqWrQ1KYmavvwCImW6x8pBy6LLTK73tzSMg7HBLbq0g/l7EkpMcxZWDOvyufuCXUqO2bj47apcOhw6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^6.32.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.38" + } + }, + "node_modules/@langchain/classic/node_modules/openai": { + "version": "6.49.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.49.0.tgz", + "integrity": "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/community": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-1.1.27.tgz", + "integrity": "sha512-s2U3w7QV7QpkFtY1eZMni4poz+nKLFclpDi3a7hUbZ67ttsGaU9WkZ2BiLuzLIs+IFaUvON/KcGkE8EqAl9aPA==", + "deprecated": "This package has been deprecated. See https://github.com/langchain-ai/langchainjs-community/issues/61 for more info", + "dev": true, + "license": "MIT", + "dependencies": { + "@langchain/classic": "1.0.27", + "@langchain/openai": "1.4.1", + "binary-extensions": "^2.2.0", + "flat": "^5.0.2", + "js-yaml": "^4.1.1", + "langsmith": ">=0.4.0 <1.0.0", + "math-expression-evaluator": "^2.0.0", + "uuid": "^10.0.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@arcjet/redact": "^v1.2.0", + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-dynamodb": "^3.1001.0", + "@aws-sdk/client-lambda": "^3.1001.0", + "@aws-sdk/client-s3": "^3.1001.0", + "@aws-sdk/client-sagemaker-runtime": "^3.1001.0", + "@aws-sdk/client-sfn": "^3.1001.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.2.0", + "@azure/storage-blob": "^12.31.0", + "@browserbasehq/sdk": "*", + "@browserbasehq/stagehand": "^1.0.0", + "@clickhouse/client": "^0.2.5", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^2.0.2", + "@gomomento/sdk-core": "^1.117.2", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^4.13.14", + "@huggingface/transformers": "^3.8.1", + "@ibm-cloud/watsonx-ai": "*", + "@lancedb/lancedb": "^0.19.1", + "@langchain/core": "^1.1.38", + "@layerup/layerup-security": "^1.5.12", + "@libsql/client": "^0.17.0", + "@mendable/firecrawl-js": "^4.15.2", + "@mlc-ai/web-llm": "*", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^5.11.1", + "@opensearch-project/opensearch": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/util-utf8": "^4.2.2", + "@spider-cloud/spider-client": "^0.2.0", + "@supabase/supabase-js": "^2.45.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "*", + "@vercel/postgres": "*", + "@writerai/writer-sdk": "^3.6.0", + "@xata.io/client": "^0.30.1", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.22.2", + "assemblyai": "^4.25.1", + "azion": "^3.1.2", + "better-sqlite3": ">=9.4.0 <13.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.5.8", + "cheerio": "^1.2.0", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "convex": "^1.32.0", + "couchbase": "^4.6.1", + "crypto-js": "^4.2.0", + "d3-dsv": "^3.0.1", + "discord.js": "^14.25.1", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "faiss-node": "*", + "fast-xml-parser": "*", + "firebase-admin": "^13.6.1", + "google-auth-library": "*", + "googleapis": "*", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", + "ignore": "^7.0.5", + "interface-datastore": "^9.0.2", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.3", + "lodash": "^4.17.23", + "lunary": "^0.7.10", + "mammoth": "^1.11.0", + "mariadb": "^3.5.1", + "mem0ai": "^2.2.4", + "mysql2": "^3.19.1", + "neo4j-driver": "*", + "node-llama-cpp": ">=3.0.0", + "notion-to-md": "^3.1.0", + "officeparser": "^6.0.4", + "openai": "*", + "pdf-parse": "2.4.5", + "pg": "^8.11.0", + "pg-copy-streams": "^7.0.0", + "pickleparser": "^0.2.1", + "playwright": "^1.58.2", + "portkey-ai": "^3.0.3", + "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", + "replicate": "*", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.28", + "typesense": "^3.0.1", + "usearch": "^1.1.1", + "voy-search": "0.6.3", + "word-extractor": "*", + "ws": "^8.14.2", + "youtubei.js": "*" + }, + "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@aws-sdk/dsql-signer": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@huggingface/transformers": { + "optional": true + }, + "@lancedb/lancedb": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@xenova/transformers": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "azion": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "couchbase": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "faiss-node": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mem0ai": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "node-llama-cpp": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-client": { + "optional": true + }, + "word-extractor": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@langchain/community/node_modules/@langchain/openai": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.4.1.tgz", + "integrity": "sha512-jaHk4TnLqWrQ1KYmavvwCImW6x8pBy6LLTK73tzSMg7HBLbq0g/l7EkpMcxZWDOvyufuCXUqO2bj47apcOhw6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^6.32.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.38" + } + }, + "node_modules/@langchain/community/node_modules/@langchain/openai/node_modules/openai": { + "version": "6.49.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.49.0.tgz", + "integrity": "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/core": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.3.2.tgz", + "integrity": "sha512-kXI5wkIRy6MVNBFzFdN4MyNAGPTsodVZFQ9BRQTyOCNnKy+qPuipMCl+qaybtN6rQ+0YrJvfTxYd+u4goAHtFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "@standard-schema/spec": "^1.1.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.11.0 <1.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@langchain/langgraph": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.6.0.tgz", + "integrity": "sha512-7ka7uXNhi9Rxt8LcIJm63+lE3J/MD4tU5TzjiAOcwpLh/2CBe5JKuvFah0MSQnD7onwBcxcEkcaGj9Omfbkxkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "^1.2.2", + "@langchain/langgraph-sdk": "~1.12.9", + "@langchain/protocol": "^0.0.20", + "@standard-schema/spec": "1.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.1.48", + "zod": "^3.25.32 || ^4.2.0" + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.2.2.tgz", + "integrity": "sha512-KL3nzi7I61ll5nANCQe5RoJ5SqTFrE+si9IK6k7IIj9OrPkIKT/Dz0w8CyJF+K7OEuJfA33HvPrZqfzzW1DXDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.1.48" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "1.12.9", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.12.9.tgz", + "integrity": "sha512-A1LkLPVu00dj+9aErXPBLhmFURHtnnRO9KRFST+xAwurqQTa+LxC9ixxF7p+ZMo5P70qMFoaP8Ud2wuZmYD4xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@langchain/protocol": "^0.0.20", + "@types/json-schema": "^7.0.15", + "p-queue": "^9.0.1", + "p-retry": "^7.1.1" + }, + "peerDependencies": { + "@langchain/core": "^1.2.18", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.5.tgz", + "integrity": "sha512-Lki/zhXFVmDhesBUn/yHRLW9Bte7mE7xoFHzTR8sIomV7a2z+LxYAPkGvdfidNiu4fKSfjtawixxSHcZci5QcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/openai": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.4.4.tgz", + "integrity": "sha512-mRr/X5rvlwPj6cSXPxbL+CtOqYANO1/+CQ3Z+5t48kWnrlgPYOazmA+UAWvqQOuwJ6LaYn3SFrt43rR4lte/Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^6.32.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.39" + } + }, + "node_modules/@langchain/openai/node_modules/openai": { + "version": "6.49.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.49.0.tgz", + "integrity": "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/protocol": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.20.tgz", + "integrity": "sha512-sxlkDG2/HGyjDFLTJ8B/o3oglRjOv8iLCFgrV0550/Vym3V6RZ0SUjAQFe8jrLQw8ZXjERy3swJp5OODBSrUYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@langchain/textsplitters": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-1.0.1.tgz", + "integrity": "sha512-rheJlB01iVtrOUzttscutRgLybPH9qR79EyzBEbf1u97ljWyuxQfCwIWK+SjoQTM9O8M7GGLLRBSYE26Jmcoww==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.0.0" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.5.tgz", + "integrity": "sha512-Pw7PVCndruF1pb3ebCxpSVpkMHSDjwRCLcTRw+RizUKco5QbWylwq2g4lnG/RYN7yTTQDXE9F77MwtkAyphrjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.12.tgz", + "integrity": "sha512-On9HU4oMMiHPMkQDgoTnGahjgG5n/MkN38CgG77sMraKLL+3JBbeQNi1FcCXV9QRqwF9/BUXP3caHyJC1fBOTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@n8n_io/ai-assistant-sdk": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@n8n_io/ai-assistant-sdk/-/ai-assistant-sdk-1.27.0.tgz", + "integrity": "sha512-xCELByG49K24I1rVx6o3FueAH1+cu66d7ojoQepkbfsphIK0HLZisTCZ3/MmgtZUjvXMUI0Ekg46NWHvMMcJtQ==", + "dev": true, + "license": "LicenseRef-n8n-enterprise", + "engines": { + "node": ">=20.15", + "pnpm": ">=8.14" + } + }, + "node_modules/@n8n_io/riot-tmpl": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.1.tgz", + "integrity": "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-riot": "^1.0.0" + } + }, + "node_modules/@n8n/ai-node-sdk": { + "version": "0.35.6", + "resolved": "https://registry.npmjs.org/@n8n/ai-node-sdk/-/ai-node-sdk-0.35.6.tgz", + "integrity": "sha512-dHvJ8mK9va2fcCTavhB33tn38z00BjQxAd3i9PpaQHjBgxU+wEzoSFGrg8MYG1wZFK1kf9XrblPbAEk6v6Y0XA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/ai-utilities": "0.38.6" + } + }, + "node_modules/@n8n/ai-utilities": { + "version": "0.38.6", + "resolved": "https://registry.npmjs.org/@n8n/ai-utilities/-/ai-utilities-0.38.6.tgz", + "integrity": "sha512-B6xRgBkkBQCZ97wLkfjg04rSmIJrkCiCiaAg6Iv6GOpYA2vAw9xzk7rxxNOs4a8NatsOhU347peods1CueEl6A==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@langchain/classic": "1.0.27", + "@langchain/community": "1.1.27", + "@langchain/core": "1.2.8", + "@langchain/openai": "1.4.4", + "@langchain/textsplitters": "1.0.1", + "@n8n/api-types": "1.45.6", + "@n8n/backend-network": "1.19.6", + "@n8n/typescript-config": "1.12.0", + "@n8n/utils": "1.52.1", + "@thednp/dommatrix": "^3.1.1", + "js-tiktoken": "1.0.21", + "langchain": "1.2.30", + "lodash": "4.18.1", + "n8n-workflow": "2.45.4", + "pdf-parse": "2.4.5", + "tmp-promise": "3.0.3", + "undici": "^7.29.1", + "zod": "3.25.76", + "zod-to-json-schema": "3.23.3" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/@langchain/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.2.8.tgz", + "integrity": "sha512-ppi2UaCYKqM4LEY8NWQ/JZ0Of0MAngHRvclceVeEQklcD/M6QKanlHPWblDnLO2m4vz7987b1Z4r33Ps6lf/ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "@standard-schema/spec": "^1.1.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.5.0 <1.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/ai-utilities/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/ai-utilities/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/api-types": { + "version": "1.45.6", + "resolved": "https://registry.npmjs.org/@n8n/api-types/-/api-types-1.45.6.tgz", + "integrity": "sha512-TWEa9KFwzi+gTo9i7lMFAADWrtSbspLLZaX/ES+7w749W0dO3IBAE/CfiCjsUpERLfXH4AcX/Livb0eQ1BNY3A==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@asteasolutions/zod-to-openapi": "7.3.4", + "@n8n_io/ai-assistant-sdk": "1.27.0", + "@n8n/inbound-auth": "0.4.4", + "@n8n/permissions": "0.82.0", + "@n8n/utils": "1.52.1", + "n8n-workflow": "2.45.4", + "validator": "13.15.26", + "xss": "1.0.15" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/api-types/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/api-types/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/api-types/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/api-types/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/api-types/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/api-types/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/api-types/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/backend-common": { + "version": "1.45.6", + "resolved": "https://registry.npmjs.org/@n8n/backend-common/-/backend-common-1.45.6.tgz", + "integrity": "sha512-BlbrP4qMpDLD+52W7VsQp0MW7PC2lhx4ULn4pz32d4SUkbzdP/ybxwNBVAmnTfYVN0RltGS0PPyNibs2Iy77Xg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/config": "2.43.2", + "@n8n/constants": "0.43.1", + "@n8n/decorators": "1.45.6", + "@n8n/di": "0.17.0", + "@n8n/utils": "1.52.1", + "callsites": "3.1.0", + "flatted": "3.4.2", + "logform": "2.6.1", + "n8n-workflow": "2.45.4", + "picocolors": "1.0.1", + "stream-json": "1.9.1", + "winston": "3.14.2", + "yargs-parser": "21.1.1" + } + }, + "node_modules/@n8n/backend-common/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/backend-common/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/backend-common/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/backend-common/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/backend-common/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/backend-common/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/backend-common/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/backend-network": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@n8n/backend-network/-/backend-network-1.19.6.tgz", + "integrity": "sha512-edPr3ZhPTrRdf7U8/w9SggvcuW6diMhFZpkKT6eMAlROCEAcGPWO4JIlgvIxDRVIgkHVWXVffC+39544gcytVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/backend-common": "1.45.6", + "@n8n/config": "2.43.2", + "@n8n/constants": "0.43.1", + "@n8n/di": "0.17.0", + "@n8n/utils": "1.52.1", + "axios": "1.20.0", + "cache-manager": "5.2.3", + "form-data": "4.0.6", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "iconv-lite": "0.6.3", + "n8n-workflow": "2.45.4", + "proxy-from-env": "^1.1.0", + "qs": "6.15.2", + "undici": "^7.29.1", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/backend-network/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/backend-network/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/backend-network/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/backend-network/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/backend-network/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/backend-network/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/backend-network/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/config": { + "version": "2.43.2", + "resolved": "https://registry.npmjs.org/@n8n/config/-/config-2.43.2.tgz", + "integrity": "sha512-LQQrLjmKui/aKLozuKroc6fnsVnVeF9m+dHL4/4raaCJT3pLoKI0pgg8qOW0cQU4aCVt2RamlWp+ijSYbwfxiQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/constants": "0.43.1", + "@n8n/di": "0.17.0", + "reflect-metadata": "0.2.2", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/constants": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@n8n/constants/-/constants-0.43.1.tgz", + "integrity": "sha512-/6c7bvBqeEf11vhIiSV6bDOpjZ+gvfvj9fVIZUyDUUjwuurtpYOOY4AESmj21F3EQE+qbrXHKNscgkHLAaikZA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@n8n/decorators": { + "version": "1.45.6", + "resolved": "https://registry.npmjs.org/@n8n/decorators/-/decorators-1.45.6.tgz", + "integrity": "sha512-EbJEnir+oqGs4tw9gzf1886TyGmDoVIuqpOgtFOTrAUe7yLDB1TbY56AceqUkH5lL2vuzWeStzxtvwhwiMX37Q==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/api-types": "1.45.6", + "@n8n/constants": "0.43.1", + "@n8n/di": "0.17.0", + "@n8n/permissions": "0.82.0", + "lodash": "4.18.1", + "n8n-workflow": "2.45.4" + } + }, + "node_modules/@n8n/decorators/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/decorators/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/decorators/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/decorators/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/decorators/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/decorators/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/decorators/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/di": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@n8n/di/-/di-0.17.0.tgz", + "integrity": "sha512-SUs8lp43QD9anRspXswYxoX8EbvdMpwIWYWoBbvw2Mf2CWymFZKSf/Gjo34KATvJLuKhkmv23dM1ntdsY48Wjg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "reflect-metadata": "0.2.2" + } + }, + "node_modules/@n8n/errors": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.6.0.tgz", + "integrity": "sha512-oVJ0lgRYJY6/aPOW2h37ea5T+nX7/wULRn5FymwYeaiYlsLdqwIQEtGwZrajpzxJB0Os74u4lSH3WWQgZCkgxQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/eslint-plugin-community-nodes": { + "version": "0.37.1", + "resolved": "https://registry.npmjs.org/@n8n/eslint-plugin-community-nodes/-/eslint-plugin-community-nodes-0.37.1.tgz", + "integrity": "sha512-16BzpuzcqyGGY4sMf3FkdnVRihhIK/d9Z70nrSdvDkRB5XYNVr2P1c2QeWqGiKdiX/X1yeRlsDAGfnqTfbNpeg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/utils": "1.52.1", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "fastest-levenshtein": "1.0.16" + }, + "peerDependencies": { + "eslint": "9.29.0", + "n8n-workflow": ">=2" + } + }, + "node_modules/@n8n/expression-runtime": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.8.0.tgz", + "integrity": "sha512-SnBLoLsrGUCS9cVrF20UuSyKcMv+y6Clc4+EA9zLjX85S0GPwOAdApUVSsi81ejPAqMlm42TW1L6r7NpcK/ztQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/tournament": "1.0.6", + "isolated-vm": "^6.0.2", + "js-base64": "3.7.2", + "jssha": "3.3.1", + "lodash": "4.17.23", + "luxon": "3.7.2", + "md5": "2.3.0", + "title-case": "3.0.3", + "transliteration": "2.3.5" + } + }, + "node_modules/@n8n/expression-runtime/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@n8n/inbound-auth": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@n8n/inbound-auth/-/inbound-auth-0.4.4.tgz", + "integrity": "sha512-nLfWtsacVRHrj7vsRLCxyre/5EfLnbqGbyHWRKXNosPAK/zl8O/4Pm8KA6wpYKO0MDrxZauF21iB/QDr6wrscw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/permissions": "0.82.0", + "n8n-workflow": "2.45.4" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/@n8n/errors": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.15.0.tgz", + "integrity": "sha512-hLg8/m2Ohs8yTUUh+1UuFFC1+Q4j06HolSoWRQIIobyf9T40xoG5beltEFYrij/psl2Lub41SVV2QTzPROiEOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/@n8n/expression-runtime": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.36.3.tgz", + "integrity": "sha512-gx4TQqbx5TwqvLNBVooZM5N8vYUuOeTq/X7fCI9Jqh8Bk2LwDvRiupg+dtsxhUl9TzZ/GeqLqQu+TXKl2vUlrg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.15.0", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "isolated-vm": "^7.0.1", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "quickjs-emscripten": "^0.32.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.76" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/@n8n/tournament": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.15.0.tgz", + "integrity": "sha512-9A69AFTsomVU0xQli7Bam89XpvQhTH8DbtpGBfJCoEPUNeMWlh59WpOw+oLEBA1W7lifyfkiJSLeUQxYArT2aA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/isolated-vm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-7.0.2.tgz", + "integrity": "sha512-Z1sYxpptGyGiKblG16EJEdP5x9/68C6AuqFFsewnrxlwSlrTLHKqtRMogOjcYZ0f8/cijMaRi+XwiHVQ1+uG+g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@n8n/inbound-auth/node_modules/n8n-workflow": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.45.4.tgz", + "integrity": "sha512-qNCBjHo4zWg5Rtz8nzVxBRS70pJYlAXoW0Y1suGR0QJofWWGDQDLxZ/iGB1DcMA8uNO9Xz9Y/lj5gAbAw6cRuw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/constants": "0.43.1", + "@n8n/errors": "0.15.0", + "@n8n/expression-runtime": "0.36.3", + "@n8n/tournament": "1.15.0", + "@n8n/utils": "1.52.1", + "ast-types": "0.16.1", + "axios": "1.20.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "generate-schema": "2.6.0", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/inbound-auth/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@n8n/node-cli": { + "version": "0.54.6", + "resolved": "https://registry.npmjs.org/@n8n/node-cli/-/node-cli-0.54.6.tgz", + "integrity": "sha512-1nKH7gQf3R58zWNXO5HI2AlMVwHEMPU2q3wIbsqrIAEuqS9TZIl6hnTFn16PE2UrpGuTr6Fv6JIHCAVvoS+Gdw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@clack/prompts": "^0.11.0", + "@eslint/js": "^9.29.0", + "@n8n/ai-node-sdk": "0.35.6", + "@n8n/eslint-plugin-community-nodes": "0.37.1", + "@oclif/core": "^4.5.2", + "change-case": "^5.4.4", + "eslint": "9.29.0", + "eslint-import-resolver-typescript": "^4.4.3", + "eslint-plugin-import-x": "^4.15.2", + "eslint-plugin-n8n-nodes-base": "^2.0.0", + "fast-glob": "3.3.3", + "handlebars": "4.7.10", + "picocolors": "1.0.1", + "prettier": "3.6.2", + "rimraf": "6.0.1", + "ts-morph": "^27.0.2", + "typescript-eslint": "8.70.0" + }, + "bin": { + "n8n-node": "bin/n8n-node.mjs" + }, + "peerDependencies": { + "eslint": ">= 9" + } + }, + "node_modules/@n8n/permissions": { + "version": "0.82.0", + "resolved": "https://registry.npmjs.org/@n8n/permissions/-/permissions-0.82.0.tgz", + "integrity": "sha512-TJ9o5expcrIgWNCBv2hUjLg5+LfnhBwXyyWlpPVckVO+5RefLme3ogUB7VD+G44q5KeQrf6/FE5wqWr+48SGAQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "zod": "3.25.76" + } + }, + "node_modules/@n8n/tournament": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.0.6.tgz", + "integrity": "sha512-UGSxYXXVuOX0yL6HTLBStKYwLIa0+JmRKiSZSCMcM2s2Wax984KWT6XIA1TR/27i7yYpDk1MY14KsTPnuEp27A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@n8n_io/riot-tmpl": "^4.0.1", + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + }, + "engines": { + "node": ">=20.15", + "pnpm": ">=9.5" + } + }, + "node_modules/@n8n/typescript-config": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@n8n/typescript-config/-/typescript-config-1.12.0.tgz", + "integrity": "sha512-WlUZSQZIjBH1LZNGstyqJ9TrjWJDLKtgmrwCly43h678rfoACbMfIuWmaehglZZ+Ohuh+ZWMBpJwCa0Pqzcd5w==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@n8n/utils": { + "version": "1.52.1", + "resolved": "https://registry.npmjs.org/@n8n/utils/-/utils-1.52.1.tgz", + "integrity": "sha512-lADWeTQwpj85oSnxsy+lOG7Pvvwl0EkWiGOrhjsjbldPlKX1HU8fWcl8sIKaLxADUaKbU55QbCByY0lfAx7U0Q==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/constants": "0.43.1", + "nanoid": "3.3.18", + "semver": "7.7.3" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "dev": true, + "license": "MIT", + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/arborist": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", + "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.1.1", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.6", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/arborist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/arborist/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", + "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", + "dev": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/package-json/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@npmcli/package-json/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@npmcli/package-json/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@npmcli/package-json/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@npmcli/package-json/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/query": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", + "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@oclif/core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.14.0.tgz", + "integrity": "sha512-QCJIZoVJxV7jywgVAUlsQwl3dr3Sa21kfi5z9KrYolbexWJUtFSEtMoNiBWojD05mYycLnB50gp/VxlGdaOCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^6", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.8.1", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.17", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0", + "wsl-utils": "^0.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/core/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "3.2.34", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.34.tgz", + "integrity": "sha512-KhbPcNjitAU7jUojMXJ3l7duWVub0L0pEr3r3bLrpJBNuIJhoIJ7p56Ropcb7OMH2xcaz5B8HGq56cTOe1FHEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oclif/core": "^4", + "ansis": "^3.16.0", + "debug": "^4.4.1", + "ejs": "^3.1.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete/node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@oclif/plugin-help": { + "version": "6.2.32", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.32.tgz", + "integrity": "sha512-LrmMdo9EMJciOvF8UurdoTcTMymv5npKtxMAyonZvhSvGR8YwCKnuHIh00+SO2mNtGOYam7f4xHnUmj2qmanyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-not-found": { + "version": "3.2.62", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.62.tgz", + "integrity": "sha512-hwUdpeExF4ZhIf0mTeegnznpfSICOr4tnk8VZ6POmEcvNEhK8OtU/zbfCQbiGNa0Zjp1oMncD1Oxh585GHUSog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.7.1", + "@oclif/core": "^4.5.2", + "ansis": "^3.17.0", + "fast-levenshtein": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-not-found/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/@oclif/plugin-version": { + "version": "2.2.32", + "resolved": "https://registry.npmjs.org/@oclif/plugin-version/-/plugin-version-2.2.32.tgz", + "integrity": "sha512-KwsEKjb202nthRIwigyvRNQ5vK8mUr/FnPzBjkLX7SbgPMLM7iqoaWDPZqBvq5/A/226Ey4vCf64y3zJh4eaWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oclif/core": "^4", + "ansis": "^3.17.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", + "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.2.2", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", + "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", + "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", + "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.1.4", + "@octokit/plugin-paginate-rest": "^11.4.2", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.3.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@pipedream/platform": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@pipedream/platform/-/platform-3.4.0.tgz", + "integrity": "sha512-Pbx50aX7V+S6SUZrBFrXftzoefDNTkcs+gfwPTz9i+evI7IMkrz9DXnC7rW6yJhXc8g83UKnlp/HX+7NK50y4Q==", + "dev": true, + "dependencies": { + "axios": "^1.15.2", + "fp-ts": "^2.0.2", + "io-ts": "^2.0.0", + "mime-types": "^3.0.1", + "querystring": "^0.2.1", + "uuid": "^11.1.0" + } + }, + "node_modules/@pipedream/platform/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@pipedream/platform/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@pipedream/platform/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", + "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.4.tgz", + "integrity": "sha512-EtMX6XjRWSastG2SdkmSQByPtJ9dx/NIjnHbpQPCYt62j4RmSx5rgLTGpw0YCjF5h191gZOmBfheOT23cRSFdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@thednp/dommatrix": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@thednp/dommatrix/-/dommatrix-3.1.2.tgz", + "integrity": "sha512-wxRH2/Qdqd+JxFpjb5vw+ZMaij8Hqa/9Hgl41n2Jb+3WNRhdemrosK0FxEo/yEvhvHGW8D1tZ3oPH9N0m1QG+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@ts-morph/common": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", + "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/inquirer": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.10.tgz", + "integrity": "sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.27", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.27.tgz", + "integrity": "sha512-VenMRv/eKNrspX9v3H4dbmjsrnCOYdYBG3kxXKk3dG6X+urBGcj2Prww3xw1U5K4FV/mE4h5GbWd0szEKf+9ew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz", + "integrity": "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/type-utils": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.70.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.0.tgz", + "integrity": "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", + "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.70.0", + "@typescript-eslint/types": "^8.70.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", + "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", + "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz", + "integrity": "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", + "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", + "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.70.0", + "@typescript-eslint/tsconfig-utils": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", + "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", + "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.13.0.tgz", + "integrity": "sha512-60E5mvmIiW+i1E/HYeSX4iE4A8s9NJSGYPu7UM0DJght1c2tTkWwuyDyAwzsEXhKM/XbbndfnTiXdfrf27d2hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.13.0.tgz", + "integrity": "sha512-qlh/JR3Tuy1cJ7xtf5pR4ykvLiK6os7X7EL2+zXZbrVc/s+yB8DoFdQrCAW9/a2TOxPVbR7QVNpkPWh7UWIsEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.13.0.tgz", + "integrity": "sha512-yynnnnJXUC99WeD1GfvuXPKsKNfvgzGYC2eRayFnUfsHVROjqrbFOkFQLZiSx1H8+PLy5BzU8vqyPyLqbRfeBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.13.0.tgz", + "integrity": "sha512-t8PXE0Ip9tmGyhRlfVsnEbzGM5iSvJIeNIXciUACjzpg0mmPs13fGv2x7W8sBa0S8zbIiohxKXS9fL/LXW/ERQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.13.0.tgz", + "integrity": "sha512-qdtl9GDWDoDx6rhzF4RU5SJutV3lcLOiTiyqD6nBxYfEO6lAgLNukfrA+HJ2u2OiyWvV8JW5iEWD3LoONw7lYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.13.0.tgz", + "integrity": "sha512-VrMUVwdrScTarsh4B6aUhTtziUavSlglo4Hi8XZ6CpAe4+j3nquY+XoP+tYkiSBDsXZ8386ne4pv4YYYBNi02w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.13.0.tgz", + "integrity": "sha512-zSbTVwaxiTC+zYJ9xVNTC6c+BYO/kOV/odcTI3bHuFFXa+XmjLoUkb/8UxsZeU5YF4K000m17wrXoTblNWuzOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.13.0.tgz", + "integrity": "sha512-Z/Rax87moZHsuvJ8awdfP+zrMlsCt3d7mggUPIiPe32KpeagimyzJWnZSMQoV67an9uVQghyh92Pc0VprITnYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.13.0.tgz", + "integrity": "sha512-oUzXz8hNvFLNEAag8lBKJe1euiV2dmGHAR+eQ5fNpsZO0F1K7IMRb4J6pqYsd7uLMRQNPiu5A1WKUJHbFLWbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.13.0.tgz", + "integrity": "sha512-BxhGYvZBg0YrvNOeSEGR3eNklwXYy0mUJk7oeGJvZoQMKM68zYvbbf52+fQI3Le4X0fW276FTKIoTwjpMob80A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.13.0.tgz", + "integrity": "sha512-nZcIuup9Ar3gAlqoZfX/KW6x83TMI11tLpCjldZG5M1KeY8qNUPNJJ71+sPvG8HMBpoh+ix1pAm5gkMFvcSAcQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.13.0.tgz", + "integrity": "sha512-AG6AgdDrkRNjAPwD/cftFJGgZUWCiJX+E8vpYCDEnwFHLTHZ7ppBiU6/vyPPwrfjcPUPQCDbbxE/eZUXsx49jQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.13.0.tgz", + "integrity": "sha512-6vOqYGcAmzpRWpUURWutzvpf1qJ6eOu5cfYObdtPJnzZ0+Kw4cw+ZkZJqIYEh8fPuTBwSYDL/FwlUhma1G/JVQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.13.0.tgz", + "integrity": "sha512-4HeIn0S2KVUowK0A+EweXrDvZ8CKHEfTtif0kx9vt3ORNsqcXNFFIFnHE0tizma3/Gt5OGrU2ovYNuj+D/bN4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.13.0.tgz", + "integrity": "sha512-JfkvqY7NZbxyhexLE+g6Z8Hsx4utHOGaVazAVghPiBn8Upw2iERZPpKYnW1FBVU4WStv/BiIsoK/Muke0E7asA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.13.0.tgz", + "integrity": "sha512-iwjLeVdZo/8v5OKAeRIC+4VQV045t1EPZr5B/AVLgUQ04IdtNthWzg6not2cNjZgH2gHpSf4fOXMmN83aLO1FA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.13.0.tgz", + "integrity": "sha512-TOh1fqOjyq1OPoht3BDLd7Y+HeMJRwd5g2VNmCQclknEbjp4py5GFRmT4L/rOybF5Af8q0LT9zHs2WGkwlqtrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.13.0.tgz", + "integrity": "sha512-3T2MNtF2dd1yjaXCVqrsi+/3VZeksEzU1hygPzqqkBLVVjn+2iJzXEkxxCKIVFrWnpmEbazeHJp/ruYD0RUVWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.13.0.tgz", + "integrity": "sha512-hKWfSLiN3L4cIxg0jrpyP/66AXLTcyD37jmSn//rGdOUYGlizYxTEFdJ+GZbp2NnaML3YS/A73nCysze8FaJdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.13.0.tgz", + "integrity": "sha512-N4gDKkDoiyIQEcn3vTqe2ksRxUAtBvQ8c5TiT+WonSa6FHgCAY5+RggnZ134hXJoRj57JEjv4/m+i/pAKHWrTQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.13.0.tgz", + "integrity": "sha512-DpLMlAqlrk4zBVK3DiDjwKD0oq7pZNy+8BiIBRuByqb6dm5jsdoLMhIiyh/xR7+WejaW/1Ui+XgbgMNa4+zWJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yeoman/adapter": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@yeoman/adapter/-/adapter-1.6.0.tgz", + "integrity": "sha512-oJw/i6zUDdfsT5VOPuANDDEWs5NhlMq5+rmyZIVdgBTXalbCw6nUfJQOblO2Fnq+Ov6CxlyzDBTWQvL5cEjA+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/inquirer": "^9.0.3", + "chalk": "^5.2.0", + "inquirer": "^9.2.2", + "log-symbols": "^7.0.0", + "ora": "^8.1.0", + "p-queue": "^8.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yeoman/adapter/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yeoman/adapter/node_modules/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@yeoman/adapter/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@yeoman/conflicter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@yeoman/conflicter/-/conflicter-2.4.0.tgz", + "integrity": "sha512-5qzY5+i16TSrcJsYshW3eKzflfYMMmVgC8gRtLCe4UxLO4u/dxpb8sN9HqQkpNVjuZV5ouIeOohSq9PSJDXC1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yeoman/transform": "^1.2.0", + "binary-extensions": "^2.3.0", + "cli-table": "^0.3.11", + "dateformat": "^5.0.3", + "diff": "^7.0.0", + "isbinaryfile": "^5.0.2", + "mem-fs-editor": "^11.1.2", + "minimatch": "^9.0.5", + "p-transform": "^4.1.6", + "pretty-bytes": "^6.1.1", + "slash": "^5.1.0", + "textextensions": "^6.11.0" + }, + "acceptDependencies": { + "@yeoman/transform": "^2.0.0", + "minimatch": "^10.0.1", + "p-transform": "^5.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@types/node": ">=18.12.0", + "@yeoman/types": "^1.0.0", + "mem-fs": "^4.0.0" + } + }, + "node_modules/@yeoman/namespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@yeoman/namespace/-/namespace-1.0.1.tgz", + "integrity": "sha512-XGdYL0HCoPvrzW7T8bxD6RbCY/B8uvR2jpOzJc/yEwTueKHwoVhjSLjVXkokQAO0LNl8nQFLVZ1aKfr2eFWZeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/@yeoman/transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@yeoman/transform/-/transform-1.2.0.tgz", + "integrity": "sha512-evb/+2XMEBoHr4BxBeFkjeVTgTS4Qe7VH8DmzZ9kgJK7C7ACPAhW/qBdsKKP1sb5MoeITSaJSVFnc8S1fjZmcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^16.18.28", + "minimatch": "^9.0.0", + "readable-stream": "^4.3.0" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/@yeoman/transform/node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yeoman/transform/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yeoman/transform/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@yeoman/transform/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@yeoman/transform/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@yeoman/transform/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@yeoman/types": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@yeoman/types/-/types-1.13.1.tgz", + "integrity": "sha512-HNwITXGjzxct9j3cvyHvCKUA/isuvtLHHGtEH5Com1vFOqYMTZZ16Skk+UM7GJDKqhTEJaIhF/xY7O2/ByQ/sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.13.0 || >=18.12.0" + }, + "peerDependencies": { + "@types/node": ">=16.18.26", + "@yeoman/adapter": "^1.6.0 || ^2.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "mem-fs": "^3.0.0 || ^4.0.0-beta.1 || ^6.0.0" + }, + "peerDependenciesMeta": { + "@yeoman/adapter": { + "optional": true + }, + "mem-fs": { + "optional": true + } + } + }, + "node_modules/@zapier/secret-scrubber": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@zapier/secret-scrubber/-/secret-scrubber-1.1.6.tgz", + "integrity": "sha512-ETblIxh6lnkVHjVqQ8XVB2Ymeo7RudR7kIiZylXtYXAfpmGLXIzbk1h9fjF/SuO21k7jdxaMBaBdqUCBHv8+og==", + "license": "ISC", + "dependencies": { + "create-hash": "1.2.0", + "lodash.isplainobject": "4.0.6" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.1.tgz", + "integrity": "sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aggregate-error/node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/archiver-utils/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/archiver-utils/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/archiver-utils/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver-utils/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/archiver-utils/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.4.tgz", + "integrity": "sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.5.tgz", + "integrity": "sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.34", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.34.tgz", + "integrity": "sha512-Aqr8mA1kIvyJ3UY5YAY7ib8LSo1DmDMbMnVqGPtsKW1LxaFQFwn9Eb3yvq2fUKJsRvaXjnAV7R5osqETapzNiA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz", + "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.33", + "caniuse-lite": "^1.0.30001819", + "electron-to-chromium": "^1.5.464", + "node-releases": "^2.0.60", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cacache/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cacache/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cache-manager": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.2.3.tgz", + "integrity": "sha512-9OErI8fksFkxAMJ8Mco0aiZSdphyd90HcKiOMJQncSlU1yq/9lHHxrT8PDayxrmr9IIIZPOAEfXuGSD7g29uog==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^9.1.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001820", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", + "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrono-node": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.8.3.tgz", + "integrity": "sha512-YukiXak31pshonVWaeJ9cZ4xxWIlbsyn5qYUkG5pQ+usZ6l22ASXDIk0kHUQkIBNOCLRevFkHJjnGKXwZNtyZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dayjs": "^1.10.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cmd-shim": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/comment-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.9.tgz", + "integrity": "sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true, + "license": "ISC" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", + "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "atomically": "^2.0.3", + "dot-prop": "^9.0.0", + "graceful-fs": "^4.2.11", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.", + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.5.tgz", + "integrity": "sha512-Co60BtcjsM5XBBbzKHdjH86rz9JCGOib8K1Vw22JQxuPhlWXI6vczIuHJo0SZEAJx27oXnd7lZR64KqyQs0GNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/ejs": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.472", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.472.tgz", + "integrity": "sha512-s4+nfT29b0FKpVKoaIxl2pWsoNcn7A/nMPSrxayw4T3BsmPeV2lF9Y06BsAQeMGhCTQ4nYJ5XST7jkqe4ZEqFw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.14.tgz", + "integrity": "sha512-pqH+jIxIhwX5U4w+Tnu3jjowTo5QTACbqef2C5Cw2RhApr7COQqgnai53u8VbtqjWRcSoAcTf7uZ62dO5kC8Iw==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "0.7.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-riot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", + "integrity": "sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", + "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.56.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.1.2", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-local/-/eslint-plugin-local-1.0.0.tgz", + "integrity": "sha512-bcwcQnKL/Iw5Vi/F2lG1he5oKD2OGjhsLmrcctkWrWq5TujgiaYb0cj3pZgr3XI54inNVnneOFdAx1daLoYLJQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-n8n-nodes-base": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-2.0.0.tgz", + "integrity": "sha512-S9ot1KzqSEN6bYyHjVE+8MrzxvnAxZRL2zEcM+ubtht2wv5OO2rQURwJ1f6vetOgMjOTPVv+B9Z5deHfe8CADw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.35.0", + "camel-case": "^4.1.2", + "eslint-plugin-local": "^1.0.0", + "indefinite": "^2.5.1", + "pascal-case": "^3.1.2", + "pluralize": "^8.0.0", + "sentence-case": "^3.0.4", + "title-case": "^3.0.3" + }, + "engines": { + "node": ">=20.15", + "pnpm": ">=9.6" + }, + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima-next": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", + "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fernet": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/fernet/-/fernet-0.3.3.tgz", + "integrity": "sha512-DvvqouVhv3VCor83wkQbSycekYUKDRQ1IKqcInaF5n5BSKgWBVfYLbSf7RRxojwQO0DZySiz5MlM2vO4MG3SUg==", + "license": "MIT", + "dependencies": { + "crypto-js": "~4.2.0", + "urlsafe-base64": "1.0.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/first-chunk-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-5.0.0.tgz", + "integrity": "sha512-WdHo4ejd2cG2Dl+sLkW79SctU7mUQDfr4s1i26ffOZRs5mgv+BRttIM9gwcq0rDbemo0KlpVPaa3LBVLqPXzcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-estree": { + "version": "0.338.0", + "resolved": "https://registry.npmjs.org/flow-estree/-/flow-estree-0.338.0.tgz", + "integrity": "sha512-ol4C6cIEcL9nep6G0qa3BuJcO5cZCH07shxNpChJ/TotKQaz5OWtn4IqgpMNF0kHISdOnMApirDwp2LwWL+KhQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/flow-parser": { + "version": "0.338.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.338.0.tgz", + "integrity": "sha512-GF+ohfrTVlvsNhxfRkON49QKG5j+5oqGb8qpGwE6mYlhIBaXDsss/JzfIxpSwVXgWtw8z8kabf/pNo1jXor5NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "flow-estree": "0.338.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fly-import": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fly-import/-/fly-import-0.4.1.tgz", + "integrity": "sha512-9gqEx0nnQ6SF0pKKOEexVNYCTBiLb3g+a5JMLkiaBIqwM/pEZH0Le83owUA/tkRLxWNwIso+sB3E+epuOCPWlw==", + "dev": true, + "dependencies": { + "@npmcli/arborist": "^7.2.0", + "env-paths": "^3.0.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz", + "integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fp-ts": { + "version": "2.16.11", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", + "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/generate-schema/-/generate-schema-2.6.0.tgz", + "integrity": "sha512-EUBKfJNzT8f91xUk5X5gKtnbdejZeE065UAJ3BCzE8VEbvwKI9Pm5jaWmqVeK1MYc1g5weAVFDTSJzN7ymtTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.9.0", + "type-of-is": "^3.4.0" + }, + "bin": { + "generate-schema": "bin/generate-schema" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-username": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-9.0.0.tgz", + "integrity": "sha512-lY7+mymwQUEhRwWTLxieKkxcZkVNnUh8iAGnl30DMB1ZtYODHkMAckZk8Jx5dLQs1YKPYM2ibnzQu02aCLFcYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/rest": "^21.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/grouped-queue": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.1.0.tgz", + "integrity": "sha512-c5NDCWO0XiXuJAhOegMiNotkDmgORN+VNo3+YHMhWpoWG/u2+8im8byqsOe3/myI9YcC//plRdqGa2AE3Qsdjw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/gulp-filter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-7.0.0.tgz", + "integrity": "sha512-ZGWtJo0j1mHfP77tVuhyqem4MRA5NfNRjoVe6VAkLGeQQ/QGo2VsFwp7zfPTGDsd1rwzBmoDHhxpE6f5B3Zuaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "multimatch": "^5.0.0", + "plugin-error": "^1.0.1", + "streamfilter": "^3.0.0", + "to-absolute-glob": "^2.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-prettier": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-prettier/-/gulp-prettier-5.0.0.tgz", + "integrity": "sha512-2TbsdXnO/zMLYwD5In8w1CwMn0fw2HOfCnD55+D+0AUczqvP8vEgwS82tEIqj4EsWnOS9+kLPclsGYkkpxxtcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "plugin-error": "^2.0.0", + "prettier": "^3.0.0", + "through2": "^4.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/gulp-prettier/node_modules/plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/hash-base/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ibm-cloud-sdk-core": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.9.2.tgz", + "integrity": "sha512-s299nBoNhr6wu+7dCP6hJ1zvowVqMNGPLyVzRDBTV2Im5HncU7kdJ3JDGMExqx+Vo9BKhVWx7/llam9ItCaKDg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/debug": "4.1.13", + "@types/node": "18.19.130", + "axios": "1.20.0", + "camelcase": "6.3.0", + "debug": "4.4.3", + "dotenv": "16.6.1", + "extend": "3.0.2", + "file-type": "21.3.4", + "form-data": "4.0.6", + "isstream": "0.1.2", + "jsonwebtoken": "9.0.3", + "load-esm": "1.0.3", + "mime-types": "2.1.35", + "retry-axios": "2.6.0", + "tough-cookie": "6.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indefinite": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/indefinite/-/indefinite-2.5.2.tgz", + "integrity": "sha512-J3ELLIk835hmgDMUfNltTCrHz9+CteTnSuXJqvxZT18wo1U2M9/QeeJMw99QdZwPEEr1DE2aBYda101Ojjdw5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/io-ts": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", + "integrity": "sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "fp-ts": "^2.5.0" + } + }, + "node_modules/ip-address": { + "version": "10.7.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz", + "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz", + "integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isolated-vm": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.2.0.tgz", + "integrity": "sha512-UuSlxSHWt2QuJ5WvBhzlIJx2VVZN/a44SqBbEZFKNdvuSyhOvhmyDo8SQ+njVbhnh/njoL/aW0bUTiFYlpweGQ==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscodeshift": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.4.0.tgz", + "integrity": "sha512-i3ESKiiTsGynxzTg5BhsZViD0ai72/6SsI1efDZxG6/5KCoElsmquxtyhXK5lpEgoO7MTNpYkjFEdEL97SkBNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "picomatch": "^4.0.2", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } + } + }, + "node_modules/jscodeshift/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jscodeshift/node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-metaschema": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-metaschema/-/json-metaschema-1.3.0.tgz", + "integrity": "sha512-FMDPEZQzqIVOQZ3OxzWryI28W6IZ9QKLcHObO9bS+SJrwnV3xQD0QtnhtgSpIZd8Xuy0xBqeRA74vfTnpK4eVg==", + "license": "Public Domain" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsonrepair": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.2.tgz", + "integrity": "sha512-Leuly0nbM4R+S5SVJk3VHfw1oxnlEK9KygdZvfUtEtTawNDyzB4qa1xWTmFt1aeoA7sXZkVTRuIixJ8bAvqVUg==", + "dev": true, + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/jsonschema": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", + "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jssha": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ky": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/langchain": { + "version": "1.2.30", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.2.30.tgz", + "integrity": "sha512-Ofsk7LTGvIkyy3uesv7hpyerpTghdjNpYFJfIxJRGGLjd+4JgTVkT/Ax6Cjg0F6doEuO7VRID0NK2QwmT3A/bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@langchain/langgraph": "^1.1.2", + "@langchain/langgraph-checkpoint": "^1.0.0", + "langsmith": ">=0.5.0 <1.0.0", + "uuid": "^11.1.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.31" + } + }, + "node_modules/langchain/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/langsmith": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.11.6.tgz", + "integrity": "sha512-5hfJM5DEZpXFxUS3U9nY2CMyBRmFnhkJj7znUODJ8vsr/Jx38tGJl4GIhV484xAY7yQFWrFOTDr33PB95kHUKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-queue": "6.6.2" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*", + "ws": ">=7" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/latest-version": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", + "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked-terminal/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/math-expression-evaluator": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-2.0.7.tgz", + "integrity": "sha512-uwliJZ6BPHRq4eiqNWxZBDzKUiS5RIynFFcgchqhBOloVLVBpZpNG8jRYkedLcBvhph8TnRyWEuxPqiQcwIdog==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mem-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-4.1.5.tgz", + "integrity": "sha512-6aWRo1jLo82ngo44tPQXjzINN7gvSNWhMU96I+O5nsJWdPi5vbUJWMTDGZDb/AVpCJRpaLGcFgrIBIpyk0DuUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "vinyl": "^3.0.1", + "vinyl-file": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "@types/vinyl": "^2.0.12" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/vinyl": { + "optional": true + } + } + }, + "node_modules/mem-fs-editor": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-11.1.4.tgz", + "integrity": "sha512-Z4QX14Ev6eOVTuVSayS5rdiOua6C3gHcFw+n9Qc7WiaVTbC+H8b99c32MYGmbQN9UFHJeI/p3lf3LAxiIzwEmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ejs": "^3.1.4", + "@types/node": ">=18", + "binaryextensions": "^6.11.0", + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^3.1.10", + "globby": "^14.0.2", + "isbinaryfile": "5.0.3", + "minimatch": "^9.0.3", + "multimatch": "^7.0.0", + "normalize-path": "^3.0.0", + "textextensions": "^6.11.0", + "vinyl": "^3.0.0" + }, + "acceptDependencies": { + "isbinaryfile": "^5.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "mem-fs": "^4.0.0" + } + }, + "node_modules/mem-fs-editor/node_modules/array-differ": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", + "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mem-fs-editor/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mem-fs-editor/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mem-fs-editor/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mem-fs-editor/node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mem-fs-editor/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mem-fs-editor/node_modules/multimatch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", + "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-differ": "^4.0.0", + "array-union": "^3.0.1", + "minimatch": "^9.0.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/multimatch/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/multimatch/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/n8n-nodes-polymorfa": { + "resolved": "n8n", + "link": true + }, + "node_modules/n8n-workflow": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.16.0.tgz", + "integrity": "sha512-JoDvHLvV4QZQBCDVQl5xkrGOmPmsacLO4TkJkErCzMmiEqIej+h14J6eCUY7gbxBj8V+GIBlpqyO63akJbXRQg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.6.0", + "@n8n/expression-runtime": "0.8.0", + "@n8n/tournament": "1.0.6", + "ast-types": "0.16.1", + "callsites": "3.1.0", + "esprima-next": "5.8.4", + "form-data": "4.0.4", + "jmespath": "0.16.0", + "js-base64": "3.7.2", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.17.23", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "10.0.0", + "xml2js": "0.6.2", + "zod": "3.25.67" + } + }, + "node_modules/n8n-workflow/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/n8n-workflow/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/n8n-workflow/node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/nan": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.30.0.tgz", + "integrity": "sha512-P5mokDkSvCGBz6mZDD/2l+w9QoICRXdCyZ9ONGmTePr40oihJg3Kpn0YU3twKd6PJnwSlLBWNQeOT6yWEV2c4A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.7.tgz", + "integrity": "sha512-jyU4cU4kVAmy/3qjP+DcoQTNXfINHc+dFw54gGqWC8nu0mpV0kOnk7kff+J+Q18SbX0k3Np8Rm7yFjd3phatNQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-patch": "lib/patch.js", + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/node-gyp/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/node-gyp/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/node-gyp/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/node-gyp/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/node-gyp/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/node-releases": { + "version": "2.0.63", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz", + "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/openapi3-ts": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.6.2.tgz", + "integrity": "sha512-gyyil3DweqhXiEhTqtAs9wwRLfWHmetwhI4kmM5Rn0d6gtmK863Ki7qqbQnp9+Wwlqoe3B1LFWj7kMp5ctE4rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.9.1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.0.tgz", + "integrity": "sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-transform": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/p-transform/-/p-transform-4.1.6.tgz", + "integrity": "sha512-ub9xBaRuk5FhfyzJv9y4OF8i7/3wH0gRG4C3jUp2pFcoftzkopgr6H0BJNcvbJBGsr7KmHHNWN+DCRPfPBIlAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/node": ">=16.18.31", + "p-queue": "^7.3.0", + "readable-stream": "^4.3.0" + }, + "engines": { + "node": ">=16.13.0" + } + }, + "node_modules/p-transform/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/p-transform/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-transform/node_modules/p-queue": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", + "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-transform/node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-transform/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", + "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ky": "^1.2.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-conflict-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-gitignore": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-0.5.1.tgz", + "integrity": "sha512-Ls8RQQrYC0HqaIugZ0lwKl+dcIeuaV+8iQwrsQFNHCEnodTXaEAM2+RxQFFv2xDaJ1gM4C05eY4u6KzIHvNayQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-unique": "^0.3.2", + "is-glob": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-gitignore/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/polymorfa-zapier": { + "resolved": "zapier", + "link": true + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.5.tgz", + "integrity": "sha512-TEHFzb5KRDKaOgJ4TYE+0jyJtFbjLvkdVOu0fZ2b6MTWzoqZd4Le+61aEppWkvTlYePgOX4ATL1wsAIPMLTw6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proggy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", + "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quickjs-emscripten": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.32.0.tgz", + "integrity": "sha512-So0Sqw869y/S2oE3Nuc0uT3Dhqgvsj8FSrwBdsuTosVsG8ME5/OcudU1GxsrIFdFABgy17GHnTVO9TYV/bLQcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-wasmfile-debug-asyncify": "0.32.0", + "@jitl/quickjs-wasmfile-debug-sync": "0.32.0", + "@jitl/quickjs-wasmfile-release-asyncify": "0.32.0", + "@jitl/quickjs-wasmfile-release-sync": "0.32.0", + "quickjs-emscripten-core": "0.32.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quickjs-emscripten-core": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.32.0.tgz", + "integrity": "sha512-QFnPfjFey8EqknSrSxe1hZrf1/8z7/6s1QzGOmKo6++02r7QRRX7ZoyNaZh7JuVjWsVW87KnQrbZqnHkOAzUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.32.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz", + "integrity": "sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/recast": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.22.0.tgz", + "integrity": "sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert": "^2.0.0", + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sort-keys": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz", + "integrity": "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.18.0" + } + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/streamfilter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^3.0.6" + }, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-buf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-3.0.1.tgz", + "integrity": "sha512-iJaWw2WroigLHzQysdc5WWeUc99p7ea7AEgB6JkY8CMyiO1yTVAA1gIlJJgORElUIR+lcZJkNl1OGChMhvc2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-utf8": "^0.2.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-bom-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-5.0.0.tgz", + "integrity": "sha512-Yo472mU+3smhzqeKlIxClre4s4pwtYZEvDNQvY/sJpnChdaxmKuwU28UVx/v1ORKNMxkmj1GBuvxJQyBk6wYMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "first-chunk-stream": "^5.0.0", + "strip-bom-buf": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "10.6.3", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.6.3.tgz", + "integrity": "sha512-MD8mENgyXMr2mDaDfzP3RWYDSyYQxIIou7HhCiCJiBekrsoDtUG0id3vYnjDfY9ndOEbazFXe13Qw6u/1AB8ew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.2.tgz", + "integrity": "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.9.0", + "bare-fs": "^4.8.2", + "fast-fifo": "^1.3.2", + "streamx": "^2.28.1" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/tldts": { + "version": "7.4.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.19.tgz", + "integrity": "sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tldts-core": "^7.4.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.20", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.20.tgz", + "integrity": "sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/transliteration": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.3.5.tgz", + "integrity": "sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yargs": "^17.5.1" + }, + "bin": { + "slugify": "dist/bin/slugify", + "transliterate": "dist/bin/transliterate" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/traverse": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", + "dev": true, + "license": "MIT", + "dependencies": { + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.5", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-morph": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", + "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.28.1", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tslib": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", + "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-of-is": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/type-of-is/-/type-of-is-3.5.1.tgz", + "integrity": "sha512-SOnx8xygcAh8lvDU2exnK2bomASfNjzB3Qz71s2tw9QnX8fkAo7aC+D0H7FV0HjRKj94CKV2Hi71kVkkO6nOxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.6.tgz", + "integrity": "sha512-r+7YvGfM5TkTiDQ5EqWi/Z4L4KP3jUI+YCmTtWg1tmTHUw6xladLJqzR2heMaHBIM0TTFLZmn/EkIaEHYs/bIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.0.tgz", + "integrity": "sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.70.0", + "@typescript-eslint/parser": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undici": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unrs-resolver": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.13.0.tgz", + "integrity": "sha512-mcPQo9Vm6kXd84d/WqMiYBi2CcA57QHBX/ev5PoUEb8b2oQzUvJ6xYsfseQblnCncgnVmCZGWiFwK7KkS5JZJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.7" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.13.0", + "@unrs/resolver-binding-android-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-x64": "1.13.0", + "@unrs/resolver-binding-freebsd-x64": "1.13.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.13.0", + "@unrs/resolver-binding-linux-loong64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-loong64-musl": "1.13.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-musl": "1.13.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-musl": "1.13.0", + "@unrs/resolver-binding-openharmony-arm64": "1.13.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.13.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.13.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.13.0" + } + }, + "node_modules/untildify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-5.0.0.tgz", + "integrity": "sha512-bOgQLUnd2G5rhzaTvh1VCI9Fo6bC5cLTpH17T5aFfamyXFYDbbdzN6IXdeoc3jBS7T9hNTmJtYUzJCJ2Xlc9gA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/update-notifier": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", + "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^8.0.1", + "chalk": "^5.3.0", + "configstore": "^7.0.0", + "is-in-ci": "^1.0.0", + "is-installed-globally": "^1.0.0", + "is-npm": "^6.0.0", + "latest-version": "^9.0.0", + "pupa": "^3.1.0", + "semver": "^7.6.3", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-5.0.0.tgz", + "integrity": "sha512-MvkPF/yA1EX7c6p+juVIvp9+Lxp70YUfNKzEWeHMKpUNVSnTZh2coaOqLxI0pmOe2V9nB+OkgFaMDkodaJUyGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/vinyl": "^2.0.7", + "strip-bom-buf": "^3.0.1", + "strip-bom-stream": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-package-manager": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/which-package-manager/-/which-package-manager-0.0.1.tgz", + "integrity": "sha512-a+bCExXd8OdYky5J59nimHxTCRPhxZSQtwKh3Ew6lpC4oY9f3KH77XDxcPrComVhSEPtvMjZigS2vZgZfgJuxA==", + "dev": true, + "dependencies": { + "execa": "^7.1.1", + "find-up": "^6.3.0", + "micromatch": "^4.0.5" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/which-package-manager/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/which-package-manager/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/which-package-manager/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/which-package-manager/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/which-package-manager/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", + "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.6.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ws": { + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.4.0.tgz", + "integrity": "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yauzl/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/yeoman-environment": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-4.4.3.tgz", + "integrity": "sha512-Irk6rymmb9vNZRhQlg8LUDeopnEot2VGlP4VayTNjayfQ3nFbiINTIjZqrtHrQdw29nTxYVOAsYy98jL1p27QQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@yeoman/adapter": "^1.4.0", + "@yeoman/conflicter": "^2.0.0-alpha.2", + "@yeoman/namespace": "^1.0.0", + "@yeoman/transform": "^1.2.0", + "@yeoman/types": "^1.1.1", + "arrify": "^3.0.0", + "chalk": "^5.3.0", + "commander": "^11.1.0", + "debug": "^4.3.4", + "execa": "^8.0.1", + "fly-import": "^0.4.0", + "globby": "^14.0.0", + "grouped-queue": "^2.0.0", + "locate-path": "^7.2.0", + "lodash-es": "^4.17.21", + "mem-fs": "^4.0.0", + "mem-fs-editor": "^11.0.0", + "semver": "^7.5.4", + "slash": "^5.1.0", + "untildify": "^5.0.0", + "which-package-manager": "^0.0.1" + }, + "acceptDependencies": { + "@yeoman/adapter": "^2.0.0", + "@yeoman/transform": "^2.1.0" + }, + "bin": { + "yoe": "bin/bin.cjs" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "peerDependencies": { + "@yeoman/types": "^1.1.1", + "mem-fs": "^4.0.0" + } + }, + "node_modules/yeoman-environment/node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/yeoman-environment/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/yeoman-environment/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-7.5.1.tgz", + "integrity": "sha512-MYncRvzSTd71BMwiUMAVhfX00sDD8DZDrmPzRxQkWuWQ0V1Qt4Rd0gS/Nee2QDTWvRjvCa+KBfiAVrtOySq+JA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/lodash-es": "^4.17.9", + "@yeoman/namespace": "^1.0.0", + "chalk": "^5.3.0", + "debug": "^4.1.1", + "execa": "^8.0.1", + "github-username": "^9.0.0", + "json-schema": "^0.4.0", + "latest-version": "^9.0.0", + "lodash-es": "^4.17.21", + "mem-fs-editor": "^11.0.1", + "minimist": "^1.2.8", + "read-package-up": "^11.0.0", + "semver": "^7.5.4", + "simple-git": "^3.20.0", + "sort-keys": "^5.0.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "peerDependencies": { + "@types/node": ">=18.18.5", + "@yeoman/types": "^1.1.1", + "mem-fs": "^4.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/yeoman-generator/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zapier-platform-cli": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/zapier-platform-cli/-/zapier-platform-cli-19.3.0.tgz", + "integrity": "sha512-AwlK4RIZlTBbkrFREoWyzfK0f81jNaMmbWHRojlVxD0ZRIdA/cWCvJDKF9Ff/7ggynNcsFt6l3yaI/vsi5b8iw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@oclif/core": "4.5.2", + "@oclif/plugin-autocomplete": "3.2.34", + "@oclif/plugin-help": "6.2.32", + "@oclif/plugin-not-found": "3.2.62", + "@oclif/plugin-version": "2.2.32", + "adm-zip": "0.6.1", + "archiver": "7.0.1", + "chrono-node": "2.8.3", + "cli-table3": "0.6.5", + "colors": "1.4.0", + "debug": "4.4.1", + "decompress-unzip": "4.0.1", + "dotenv": "17.2.1", + "esbuild": "0.25.8", + "fs-extra": "11.3.1", + "gulp-filter": "7.0.0", + "gulp-prettier": "5.0.0", + "ignore": "7.0.5", + "inquirer": "8.2.5", + "jscodeshift": "^17.3.0", + "lodash": "4.18.1", + "luxon": "3.7.1", + "marked": "15.0.12", + "marked-terminal": "7.3.0", + "open": "10.2.0", + "ora": "5.4.0", + "parse-gitignore": "0.5.1", + "prettier": "3.6.2", + "read": "4.1.0", + "semver": "7.7.2", + "string-length": "4.0.2", + "through2": "4.0.2", + "tmp": "0.2.6", + "traverse": "0.6.11", + "update-notifier": "7.3.1", + "yeoman-environment": "4.4.3", + "yeoman-generator": "7.5.1" + }, + "bin": { + "zapier-platform": "src/bin/run" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/zapier-platform-cli/node_modules/@oclif/core": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.5.2.tgz", + "integrity": "sha512-eQcKyrEcDYeZJKu4vUWiu0ii/1Gfev6GF4FsLSgNez5/+aQyAUCjg3ZWlurf491WiYZTXCWyKAxyPWk8DKv2MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.0", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^9.0.5", + "semver": "^7.6.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/zapier-platform-cli/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/zapier-platform-cli/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/zapier-platform-cli/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/zapier-platform-cli/node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/zapier-platform-cli/node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/zapier-platform-cli/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/zapier-platform-cli/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zapier-platform-cli/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zapier-platform-cli/node_modules/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/zapier-platform-cli/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zapier-platform-cli/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/zapier-platform-cli/node_modules/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/zapier-platform-core": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/zapier-platform-core/-/zapier-platform-core-19.3.0.tgz", + "integrity": "sha512-LDR6hd82wtTq78iR6KJ6UMe/mqDaJWYwETTlk0mxPZ+W8C6MnRhRBJ56QXTe3TKSj5M3yO21W686f0Z3LCLhRw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@zapier/secret-scrubber": "^1.1.2", + "content-disposition": "0.5.4", + "dotenv": "17.2.1", + "fernet": "^0.3.3", + "form-data": "4.0.6", + "json-schema-to-ts": "3.1.1", + "lodash": "4.18.1", + "mime-types": "3.0.1", + "node-abort-controller": "3.1.1", + "node-fetch": "2.7.0", + "oauth-sign": "0.9.0", + "semver": "7.7.2", + "zapier-platform-schema": "19.3.0" + }, + "engines": { + "node": ">=16", + "npm": ">=5.6.0" + }, + "optionalDependencies": { + "@types/node": "^20.3.1" + }, + "peerDependencies": { + "zapier-platform-legacy-scripting-runner": ">=3" + }, + "peerDependenciesMeta": { + "zapier-platform-legacy-scripting-runner": { + "optional": true + } + } + }, + "node_modules/zapier-platform-core/node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/zapier-platform-core/node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/zapier-platform-core/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/zapier-platform-core/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/zapier-platform-core/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/zapier-platform-schema": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/zapier-platform-schema/-/zapier-platform-schema-19.3.0.tgz", + "integrity": "sha512-i+JO3Ty9TUdBmPksEfgus/FybMNPm9Oia7up0QiyDEDBQkEfFuL6wx2k9IlP7zQbDvEQ86DYEgm0l3y2EX8QTA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "json-metaschema": "1.3.0", + "jsonschema": "1.5.0", + "lodash": "4.18.1" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.23.3", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.3.tgz", + "integrity": "sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } + }, + "zapier": { + "name": "polymorfa-zapier", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "zapier-platform-core": "19.3.0" + }, + "engines": { + "node": "22.x" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a71bafc --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "polymorfa-integrations", + "private": true, + "version": "0.1.0", + "license": "MIT", + "packageManager": "npm@11.20.0", + "engines": { "node": ">=22 <25" }, + "workspaces": ["n8n", "zapier"], + "scripts": { + "generate": "node scripts/generate.mjs", + "check:generated": "node scripts/generate.mjs --check", + "check:contracts": "node scripts/check-contracts.mjs", + "check:upstream": "node scripts/check-upstream.mjs", + "check:sdk": "node scripts/verify-sdk.mjs", + "build": "npm run generate && npm run build --workspace n8n-nodes-polymorfa", + "test": "node --test --test-reporter=spec tests/*.test.cjs tests/*.test.mjs", + "check": "npm run check:contracts && npm run check:generated && npm run build && npm test", + "validate:zapier": "npm run validate --workspace polymorfa-zapier" + }, + "devDependencies": { + "@n8n/node-cli": "0.54.6", + "@pipedream/platform": "3.4.0", + "@types/node": "22.19.1", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "typescript": "5.9.3", + "zapier-platform-cli": "19.3.0" + } +} diff --git a/pipedream/README.md b/pipedream/README.md new file mode 100644 index 0000000..f655617 --- /dev/null +++ b/pipedream/README.md @@ -0,0 +1,28 @@ +# Polymorfa for Pipedream + +Connect-compatible component source for a dedicated Polymorfa app record. No app +record has been requested and no registry PR has been submitted. The app slug +`polymorfa` is the requested identifier; its actual assignment is provider-owned. + +The requested custom authentication fields are `api_key` (a secret project token) +and `project_id` (its project UUID). Verify credentials through +`GET /platform/sessions?projectId=...` with the accepted `Polymorfa-Version`. +This needs `sessions:read`. Restrict each connection's remaining scopes to its +actions and hooks. Never use dashboard cookies or client-token impersonation. + +Each native action uses `polymorfa.app.mjs` and the provider's Axios helper with +redirects disabled. Each source owns its hook ID, authenticates the per-source +verifier and emits the public event envelope with `id` as its native dedupe key. +Confirm lifecycle, log redaction, Connect actions/events and MCP tools on the real +assigned app record before acceptance. + +After the provider creates the app directory, prepare the contribution under its +registry conventions, retain MIT attribution and submit the components for review. +An unmerged contribution is not marketplace or Connect availability. No purchase +or embedding of Pipedream Connect is implied by listing Polymorfa as an app. + +Examples target Connect action/tool/event use. Do not create recipes around the +Workflows builder scheduled to retire on March 31, 2027. + +Pipedream processing has its own residency and retention. Do not imply that a +Polymorfa EU/Brazil Number places external component execution in that region. diff --git a/pipedream/actions/apiRequest/apiRequest.mjs b/pipedream/actions/apiRequest/apiRequest.mjs new file mode 100644 index 0000000..642afd1 --- /dev/null +++ b/pipedream/actions/apiRequest/apiRequest.mjs @@ -0,0 +1,53 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-apiRequest", name: "Make an API Call", description: "Performs an authorized call to a supported public Polymorfa operation.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "method": { + "type": "string", + "label": "Method", + "optional": false, + "default": "GET", + "options": [ + { + "value": "GET", + "label": "GET" + }, + { + "value": "POST", + "label": "POST" + } + ] + }, + "path": { + "type": "string", + "label": "Path", + "optional": false, + "description": "A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused." + }, + "requestBody": { + "type": "string", + "label": "Request Body", + "optional": true, + "description": "JSON object. Authentication and version headers are supplied by the connection.", + "default": "{}" + }, + "idempotencyKey": { + "type": "string", + "label": "Idempotency Key", + "optional": true, + "description": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + "writeCreatedAt": { + "type": "string", + "label": "Write Started At", + "optional": true, + "description": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["method","path","requestBody","idempotencyKey","writeCreatedAt"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "apiRequest", input); + }, +}; diff --git a/pipedream/actions/listNumbers/listNumbers.mjs b/pipedream/actions/listNumbers/listNumbers.mjs new file mode 100644 index 0000000..91dadba --- /dev/null +++ b/pipedream/actions/listNumbers/listNumbers.mjs @@ -0,0 +1,11 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-listNumbers", name: "List Numbers", description: "Lists Numbers visible to the connected project.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{} }, + async run({ $ }) { + const input = Object.fromEntries([].map(key => [key, this[key]])); + return this.polymorfa.execute($, "listNumbers", input); + }, +}; diff --git a/pipedream/actions/listTemplates/listTemplates.mjs b/pipedream/actions/listTemplates/listTemplates.mjs new file mode 100644 index 0000000..b2d9486 --- /dev/null +++ b/pipedream/actions/listTemplates/listTemplates.mjs @@ -0,0 +1,17 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-listTemplates", name: "List Templates", description: "Lists message templates for a project.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "projectSlug": { + "type": "string", + "label": "Project Slug", + "optional": false + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["projectSlug"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "listTemplates", input); + }, +}; diff --git a/pipedream/actions/react/react.mjs b/pipedream/actions/react/react.mjs new file mode 100644 index 0000000..99192d1 --- /dev/null +++ b/pipedream/actions/react/react.mjs @@ -0,0 +1,67 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-react", name: "React to Message", description: "Adds or removes a reaction on a message.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "session": { + "type": "string", + "label": "Number", + "optional": false, + "description": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + "recipientType": { + "type": "string", + "label": "Recipient Type", + "optional": false, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + "recipient": { + "type": "string", + "label": "Recipient", + "optional": false, + "description": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + "messageId": { + "type": "string", + "label": "Message ID", + "optional": false + }, + "reaction": { + "type": "string", + "label": "Reaction", + "optional": false, + "description": "An emoji, or an empty string to remove the reaction." + }, + "idempotencyKey": { + "type": "string", + "label": "Idempotency Key", + "optional": false, + "description": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + "writeCreatedAt": { + "type": "string", + "label": "Write Started At", + "optional": false, + "description": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["session","recipientType","recipient","messageId","reaction","idempotencyKey","writeCreatedAt"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "react", input); + }, +}; diff --git a/pipedream/actions/sendMedia/sendMedia.mjs b/pipedream/actions/sendMedia/sendMedia.mjs new file mode 100644 index 0000000..b1586e0 --- /dev/null +++ b/pipedream/actions/sendMedia/sendMedia.mjs @@ -0,0 +1,118 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-sendMedia", name: "Send Media", description: "Sends an image, video, file or voice message.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "session": { + "type": "string", + "label": "Number", + "optional": false, + "description": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + "recipientType": { + "type": "string", + "label": "Recipient Type", + "optional": false, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + "recipient": { + "type": "string", + "label": "Recipient", + "optional": false, + "description": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + "mediaType": { + "type": "string", + "label": "Media Type", + "optional": false, + "default": "image", + "options": [ + { + "value": "image", + "label": "Image" + }, + { + "value": "video", + "label": "Video" + }, + { + "value": "file", + "label": "File" + }, + { + "value": "voice", + "label": "Voice" + } + ] + }, + "mediaSource": { + "type": "string", + "label": "Media Source", + "optional": false, + "default": "url", + "options": [ + { + "value": "url", + "label": "HTTPS URL" + }, + { + "value": "base64", + "label": "Base64 Bytes" + } + ] + }, + "media": { + "type": "string", + "label": "Media", + "optional": false, + "description": "HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower." + }, + "mimeType": { + "type": "string", + "label": "MIME Type", + "optional": true + }, + "caption": { + "type": "string", + "label": "Caption", + "optional": true + }, + "filename": { + "type": "string", + "label": "Filename", + "optional": true, + "description": "Used for file messages." + }, + "idempotencyKey": { + "type": "string", + "label": "Idempotency Key", + "optional": false, + "description": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + "writeCreatedAt": { + "type": "string", + "label": "Write Started At", + "optional": false, + "description": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["session","recipientType","recipient","mediaType","mediaSource","media","mimeType","caption","filename","idempotencyKey","writeCreatedAt"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "sendMedia", input); + }, +}; diff --git a/pipedream/actions/sendTemplate/sendTemplate.mjs b/pipedream/actions/sendTemplate/sendTemplate.mjs new file mode 100644 index 0000000..5d96166 --- /dev/null +++ b/pipedream/actions/sendTemplate/sendTemplate.mjs @@ -0,0 +1,74 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-sendTemplate", name: "Send Template", description: "Sends a template with its approved name and language.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "session": { + "type": "string", + "label": "Number", + "optional": false, + "description": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + "recipientType": { + "type": "string", + "label": "Recipient Type", + "optional": false, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + "recipient": { + "type": "string", + "label": "Recipient", + "optional": false, + "description": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + "templateName": { + "type": "string", + "label": "Template Name", + "optional": false + }, + "language": { + "type": "string", + "label": "Template Language", + "optional": false, + "description": "The exact language code for the selected template." + }, + "components": { + "type": "string", + "label": "Template Components", + "optional": true, + "description": "JSON array matching the template definition.", + "default": "[]" + }, + "idempotencyKey": { + "type": "string", + "label": "Idempotency Key", + "optional": false, + "description": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + "writeCreatedAt": { + "type": "string", + "label": "Write Started At", + "optional": false, + "description": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["session","recipientType","recipient","templateName","language","components","idempotencyKey","writeCreatedAt"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "sendTemplate", input); + }, +}; diff --git a/pipedream/actions/sendText/sendText.mjs b/pipedream/actions/sendText/sendText.mjs new file mode 100644 index 0000000..3b810e2 --- /dev/null +++ b/pipedream/actions/sendText/sendText.mjs @@ -0,0 +1,67 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-sendText", name: "Send Text", description: "Sends a text message or replies to a message.", + version: '0.1.0', type: 'action', + props: { polymorfa, ...{ + "session": { + "type": "string", + "label": "Number", + "optional": false, + "description": "The exact Number identifier from Polymorfa. Keep it as a string." + }, + "recipientType": { + "type": "string", + "label": "Recipient Type", + "optional": false, + "default": "phoneNumber", + "options": [ + { + "value": "phoneNumber", + "label": "Phone Number" + }, + { + "value": "id", + "label": "Conversation ID" + }, + { + "value": "bsuid", + "label": "Business-scoped User ID" + } + ] + }, + "recipient": { + "type": "string", + "label": "Recipient", + "optional": false, + "description": "Use an international phone number including +, a conversation ID, or a business-scoped user ID." + }, + "text": { + "type": "string", + "label": "Text", + "optional": false + }, + "quotedMessageId": { + "type": "string", + "label": "Reply to Message ID", + "optional": true, + "description": "Use the Polymorfa message ID, not a WhatsApp transport reference." + }, + "idempotencyKey": { + "type": "string", + "label": "Idempotency Key", + "optional": false, + "description": "Required for writes. Map a stable business key unique to this write. Preserve it across retries." + }, + "writeCreatedAt": { + "type": "string", + "label": "Write Started At", + "optional": false, + "description": "Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation." + } +} }, + async run({ $ }) { + const input = Object.fromEntries(["session","recipientType","recipient","text","quotedMessageId","idempotencyKey","writeCreatedAt"].map(key => [key, this[key]])); + return this.polymorfa.execute($, "sendText", input); + }, +}; diff --git a/pipedream/common/catalog.cjs b/pipedream/common/catalog.cjs new file mode 100644 index 0000000..e7ed5c9 --- /dev/null +++ b/pipedream/common/catalog.cjs @@ -0,0 +1,43 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const actions = { + sendText: { label: 'Send Text', description: 'Sends a text message or replies to a message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'text', 'quotedMessageId', 'idempotencyKey', 'writeCreatedAt'] }, + sendTemplate: { label: 'Send Template', description: 'Sends a template with its approved name and language.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'templateName', 'language', 'components', 'idempotencyKey', 'writeCreatedAt'] }, + sendMedia: { label: 'Send Media', description: 'Sends an image, video, file or voice message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'mediaType', 'mediaSource', 'media', 'mimeType', 'caption', 'filename', 'idempotencyKey', 'writeCreatedAt'] }, + react: { label: 'React to Message', description: 'Adds or removes a reaction on a message.', operation: 'sendReaction', fields: ['session', 'recipientType', 'recipient', 'messageId', 'reaction', 'idempotencyKey', 'writeCreatedAt'] }, + listNumbers: { label: 'List Numbers', description: 'Lists Numbers visible to the connected project.', operation: 'listPlatformSessions', fields: [] }, + listTemplates: { label: 'List Templates', description: 'Lists message templates for a project.', operation: 'listProjectTemplates', fields: ['projectSlug'] }, + apiRequest: { label: 'Make an API Call', description: 'Performs an authorized call to a supported public Polymorfa operation.', operation: null, fields: ['method', 'path', 'requestBody', 'idempotencyKey', 'writeCreatedAt'] }, +}; +const fields = { + session: { label: 'Number', required: true, help: 'The exact Number identifier from Polymorfa. Keep it as a string.' }, + recipientType: { label: 'Recipient Type', required: true, options: { phoneNumber: 'Phone Number', id: 'Conversation ID', bsuid: 'Business-scoped User ID' }, default: 'phoneNumber' }, + recipient: { label: 'Recipient', required: true, help: 'Use an international phone number including +, a conversation ID, or a business-scoped user ID.' }, + text: { label: 'Text', required: true }, + quotedMessageId: { label: 'Reply to Message ID', help: 'Use the Polymorfa message ID, not a WhatsApp transport reference.' }, + templateName: { label: 'Template Name', required: true }, + language: { label: 'Template Language', required: true, help: 'The exact language code for the selected template.' }, + components: { label: 'Template Components', json: true, default: '[]', help: 'JSON array matching the template definition.' }, + mediaType: { label: 'Media Type', required: true, options: { image: 'Image', video: 'Video', file: 'File', voice: 'Voice' }, default: 'image' }, + mediaSource: { label: 'Media Source', required: true, options: { url: 'HTTPS URL', base64: 'Base64 Bytes' }, default: 'url' }, + media: { label: 'Media', required: true, help: 'HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower.' }, + mimeType: { label: 'MIME Type' }, + caption: { label: 'Caption' }, + filename: { label: 'Filename', help: 'Used for file messages.' }, + messageId: { label: 'Message ID', required: true }, + reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, + writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, + projectSlug: { label: 'Project Slug', required: true }, + method: { label: 'Method', required: true, options: { GET: 'GET', POST: 'POST' }, default: 'GET' }, + path: { label: 'Path', required: true, help: 'A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused.' }, + requestBody: { label: 'Request Body', json: true, default: '{}', help: 'JSON object. Authentication and version headers are supplied by the connection.' }, +}; +const events = { + messageReceived: { label: 'New Message', description: 'Triggers when a message is received.', types: ['message.received'] }, + messageState: { label: 'Message State Changed', description: 'Triggers when a message is sent, acknowledged, failed or updated.', types: ['message.sent', 'message.ack', 'message.failed', 'message.update'] }, + numberState: { label: 'Number State Changed', description: 'Triggers when a Number changes state, connects or logs out.', types: ['session.status', 'session.connected', 'session.logged_out'] }, + callEnded: { label: 'Call Ended', description: 'Triggers when a call ends.', types: ['call.ended'] }, +}; +module.exports = { actions, fields, events }; diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs new file mode 100644 index 0000000..3df9eb8 --- /dev/null +++ b/pipedream/common/core.cjs @@ -0,0 +1,129 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const ORIGIN = 'https://api.polymorfa.com'; +const VERSION = '2026-09-22'; +const MAX_MEDIA_BYTES = 4 * 1024 * 1024; +const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +function object(value, name) { + let parsed = value; + try { if (typeof value === 'string') parsed = JSON.parse(value); } catch { throw new IntegrationError(`invalid_${name}_json`); } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new IntegrationError(`invalid_${name}`); + return parsed; +} +function conversation(p) { + if (!['phoneNumber', 'id', 'bsuid'].includes(p.recipientType)) throw new IntegrationError('invalid_recipient_type'); + required(p.recipient, 'recipient'); + if (p.recipientType === 'phoneNumber' && !/^\+[1-9]\d{1,14}$/.test(p.recipient)) throw new IntegrationError('invalid_phone_number'); + if (p.recipientType === 'id' && !/^[1-9]\d{0,18}$/.test(p.recipient)) throw new IntegrationError('invalid_conversation_id'); + if (p.recipientType === 'bsuid' && !/^[A-Z]{2}\.[A-Za-z0-9]{1,128}$/.test(p.recipient)) throw new IntegrationError('invalid_bsuid'); + return { [p.recipientType]: p.recipient }; +} +function messageBody(action, p) { + const body = { conversation: conversation(p) }; + if (action === 'sendText') { + body.content = { text: required(p.text, 'text') }; + if (p.quotedMessageId) { + if (!/^[1-9]\d{0,18}$/.test(required(p.quotedMessageId, 'quoted_message_id'))) throw new IntegrationError('invalid_quoted_message_id'); + body.quotedMessage = { id: p.quotedMessageId }; + } + } else if (action === 'sendTemplate') { + let components; + try { components = typeof p.components === 'string' ? JSON.parse(p.components || '[]') : (p.components ?? []); } catch { throw new IntegrationError('invalid_components_json'); } + if (!Array.isArray(components) || components.length > 1000) throw new IntegrationError('invalid_components'); + body.content = { template: { name: required(p.templateName, 'template_name'), language: required(p.language, 'language'), components } }; + if (p.language.length < 2) throw new IntegrationError('invalid_language'); + } else if (action === 'sendMedia') { + if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); + required(p.media, 'media'); + if (p.mediaSource === 'url') { + let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } + if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + throw new IntegrationError('invalid_or_oversized_media'); + } + const media = { [p.mediaSource]: p.media }; + for (const key of ['mimeType', 'caption']) if (p[key]) media[key] = required(p[key], key); + if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); + body.content = { [p.mediaType]: media }; + } else if (action === 'react') { + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = p.reaction; + } else throw new IntegrationError('unknown_action'); + return body; +} +function writeHeaders(p, now = Date.now()) { + const key = required(p.idempotencyKey, 'idempotency_key'); + if (Buffer.byteLength(key, 'utf8') > 255 || /[\x00-\x1f\x7f]/.test(key)) throw new IntegrationError('invalid_idempotency_key'); + const created = Date.parse(required(p.writeCreatedAt, 'write_started_at')); + if (!Number.isFinite(created) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(p.writeCreatedAt) || created > now + 60000 || now - created >= MAX_WRITE_AGE) throw new IntegrationError('write_requires_reconciliation'); + return { 'Idempotency-Key': key }; +} +function headers(auth, provider) { + const token = required(auth.apiKey, 'project_token'); + if (/[\r\n]/.test(token)) throw new IntegrationError('invalid_project_token'); + segment(auth.projectId, 'project_id'); + return { Authorization: `Bearer ${token}`, 'Polymorfa-Version': VERSION, 'Content-Type': 'application/json', 'User-Agent': `polymorfa-${provider}/0.1.0` }; +} +function advanced(p, auth) { + const path = required(p.path, 'path'); + if (!path.startsWith('/') || path.startsWith('//') || /[%\\?#\s]/.test(path) || path.split('/').some(x => x === '.' || x === '..')) throw new IntegrationError('unsupported_path'); + const method = p.method; + const allowed = (method === 'GET' && (path === '/platform/sessions' || /^\/messaging\/projects\/[A-Za-z0-9_-]+\/templates(?:\/[A-Za-z0-9_-]+)?$/.test(path))) || + (method === 'POST' && /^\/messaging\/[A-Za-z0-9_-]+\/messages\/(send|react)$/.test(path)); + if (!allowed) throw new IntegrationError('unsupported_operation'); + const body = method === 'POST' ? object(p.requestBody, 'request_body') : undefined; + if (body) { + const keys = path.endsWith('/react') ? ['conversation', 'id', 'reaction'] : ['conversation', 'isForwarded', 'mentions', 'quotedMessage', 'content']; + if (Object.keys(body).some(k => !keys.includes(k))) throw new IntegrationError('unsupported_request_field'); + } + return { method, url: ORIGIN + path, ...(body ? { body } : {}), ...(path === '/platform/sessions' ? { query: { projectId: auth.projectId } } : {}) }; +} +function buildRequest(action, p, auth, provider = 'integration', now) { + let r; + if (['sendText', 'sendTemplate', 'sendMedia', 'react'].includes(action)) r = { method: 'POST', url: `${ORIGIN}/messaging/${segment(p.session, 'number')}/messages/${action === 'react' ? 'react' : 'send'}`, body: messageBody(action, p) }; + else if (action === 'listNumbers') r = { method: 'GET', url: ORIGIN + '/platform/sessions', query: { projectId: auth.projectId } }; + else if (action === 'listTemplates') r = { method: 'GET', url: `${ORIGIN}/messaging/projects/${segment(p.projectSlug, 'project_slug')}/templates` }; + else if (action === 'apiRequest') r = advanced(p, auth); + else throw new IntegrationError('unknown_action'); + r.headers = { ...headers(auth, provider), ...(r.method === 'POST' ? writeHeaders(p, now) : {}) }; + return r; +} +function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; } +function parseResponse(response) { + const status = response.status; + let body = response.body; + try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + if (status < 200 || status >= 300) { + const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); + const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); + const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + } + if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); + return body; +} +async function execute(action, p, auth, transport, provider, now) { + const request = buildRequest(action, p, auth, provider, now); + let response; + try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } + return parseResponse(response); +} +module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/pipedream/common/hooks.cjs b/pipedream/common/hooks.cjs new file mode 100644 index 0000000..0ddb3f9 --- /dev/null +++ b/pipedream/common/hooks.cjs @@ -0,0 +1,92 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const { createHmac, timingSafeEqual } = require('node:crypto'); +const { events } = require('./catalog.cjs'); +const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); +function identity(auth, group, subscriptionKey) { + if (!events[group]) throw new IntegrationError('invalid_event_group'); + required(subscriptionKey, 'subscription_key'); + if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); + const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); + const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; +} +function callback(url) { + let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } + if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); + return url; +} +function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { + const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); + const types = events[group].types; + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && + hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); +} +async function call(auth, transport, provider, method, path, body, key, query) { + let response; + try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } + catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } + return parseResponse(response); +} +async function find(auth, url, group, subscriptionKey, transport, provider) { + let cursor; const seen = new Set(); const matches = []; + for (let i = 0; i < 100; i++) { + const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); + if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); + const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); + if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + for (const hook of r.data) { + const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); + if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); + } + if (!r.page?.hasMore) { + if (matches.length > 1) throw new IntegrationError('duplicate_owned_subscriptions'); + return matches[0]; + } + if (typeof r.page.nextCursor !== 'string' || seen.has(r.page.nextCursor)) throw new IntegrationError('invalid_subscription_pagination'); + cursor = r.page.nextCursor; seen.add(cursor); + } + throw new IntegrationError('subscription_inventory_requires_reconciliation'); +} +async function subscribe(auth, url, group, subscriptionKey, transport, provider) { + callback(url); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const existing = await find(auth, url, group, subscriptionKey, transport, provider); + if (existing) return record(existing); + const marker = identity(auth, group, subscriptionKey); + // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + try { + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + return record(r.data.webhook); + } catch { + // A lost response may follow a successful create. Reconcile; never blindly create again. + const recovered = await find(auth, url, group, subscriptionKey, transport, provider); + if (recovered) return record(recovered); + throw new IntegrationError('subscription_requires_reconciliation'); + } +} +async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { + const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; + let hook; + try { hook = (await call(auth, transport, provider, 'GET', path)).data; } + catch (error) { if (error.status === 404) return; throw error; } + // Disabled hooks remain owned and may be deleted; all other ownership checks still apply. + if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); + await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); +} +function authenticate(auth, group, subscriptionKey, incomingHeaders) { + const expected = identity(auth, group, subscriptionKey); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; + return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); +} +function envelope(body, group) { + if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); + // Copy the defined envelope only. Never export arbitrary top-level transport fields. + return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; +} +module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/pipedream/common/samples.json b/pipedream/common/samples.json new file mode 100644 index 0000000..a5b25e2 --- /dev/null +++ b/pipedream/common/samples.json @@ -0,0 +1,65 @@ +{ + "messageReceived": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.received", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "fromMe": false, + "timestamp": 1793534400, + "pushName": "Synthetic contact", + "isGroup": false, + "type": "text", + "text": "Synthetic setup sample. No customer message was read." + } + }, + "messageState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a72", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.sent", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "type": "text", + "timestamp": 1793534400 + } + }, + "numberState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a73", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "session.status", + "payload": { + "status": "connected" + } + }, + "callEnded": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a74", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "call.ended", + "payload": { + "from": { + "id": "739182640518205" + }, + "callId": "SYNTHETIC-CALL", + "durationSeconds": 60, + "reason": "ended", + "direction": "inbound", + "hadVideo": false + } + } +} diff --git a/pipedream/polymorfa.app.mjs b/pipedream/polymorfa.app.mjs new file mode 100644 index 0000000..802d89e --- /dev/null +++ b/pipedream/polymorfa.app.mjs @@ -0,0 +1,24 @@ +import { axios } from '@pipedream/platform'; +import core from './common/core.cjs'; +import hooks from './common/hooks.cjs'; + +export default { + type: 'app', app: 'polymorfa', + propDefinitions: { + session: { type: 'string', label: 'Number', description: 'Exact Polymorfa Number identifier, kept as a string.' }, + }, + methods: { + auth() { return { apiKey: this.$auth.api_key, projectId: this.$auth.project_id }; }, + transport($) { + return async request => { + const result = await axios($, { url: request.url, method: request.method, headers: request.headers, params: request.query, data: request.body, maxRedirects: 0, validateStatus: () => true, returnFullResponse: true, timeout: 25000 }); + return { status: result.status, headers: result.headers, body: result.data }; + }; + }, + execute($, action, input) { return core.execute(action, input, this.auth(), this.transport($), 'pipedream'); }, + subscribe($, url, group, key) { return hooks.subscribe(this.auth(), url, group, key, this.transport($), 'pipedream'); }, + unsubscribe($, owned) { return hooks.unsubscribe(this.auth(), owned.id, owned.url, owned.group, owned.subscriptionKey, this.transport($), 'pipedream', owned.markerName); }, + authenticate(group, key, headers) { return hooks.authenticate(this.auth(), group, key, headers); }, + envelope(body, group) { return hooks.envelope(body, group); }, + }, +}; diff --git a/pipedream/sources/callEnded/callEnded.mjs b/pipedream/sources/callEnded/callEnded.mjs new file mode 100644 index 0000000..b868f06 --- /dev/null +++ b/pipedream/sources/callEnded/callEnded.mjs @@ -0,0 +1,25 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-callEnded", name: "Call Ended", description: "Triggers when a call ends.", + version: '0.1.0', type: 'source', dedupe: 'unique', + props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + hooks: { + async activate() { + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "callEnded", this.subscriptionKey); + this.db.set('ownedSubscription', owned); + }, + async deactivate() { + const owned = this.db.get('ownedSubscription'); + if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + }, + }, + async run(event) { + if (!this.polymorfa.authenticate("callEnded", this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; } + let data; + try { data = this.polymorfa.envelope(event.body, "callEnded"); } catch { this.http.respond({ status: 400 }); return; } + this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) }); + this.http.respond({ status: 200 }); + }, +}; diff --git a/pipedream/sources/messageReceived/messageReceived.mjs b/pipedream/sources/messageReceived/messageReceived.mjs new file mode 100644 index 0000000..1edf127 --- /dev/null +++ b/pipedream/sources/messageReceived/messageReceived.mjs @@ -0,0 +1,25 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-messageReceived", name: "New Message", description: "Triggers when a message is received.", + version: '0.1.0', type: 'source', dedupe: 'unique', + props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + hooks: { + async activate() { + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageReceived", this.subscriptionKey); + this.db.set('ownedSubscription', owned); + }, + async deactivate() { + const owned = this.db.get('ownedSubscription'); + if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + }, + }, + async run(event) { + if (!this.polymorfa.authenticate("messageReceived", this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; } + let data; + try { data = this.polymorfa.envelope(event.body, "messageReceived"); } catch { this.http.respond({ status: 400 }); return; } + this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) }); + this.http.respond({ status: 200 }); + }, +}; diff --git a/pipedream/sources/messageState/messageState.mjs b/pipedream/sources/messageState/messageState.mjs new file mode 100644 index 0000000..b215cb0 --- /dev/null +++ b/pipedream/sources/messageState/messageState.mjs @@ -0,0 +1,25 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-messageState", name: "Message State Changed", description: "Triggers when a message is sent, acknowledged, failed or updated.", + version: '0.1.0', type: 'source', dedupe: 'unique', + props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + hooks: { + async activate() { + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageState", this.subscriptionKey); + this.db.set('ownedSubscription', owned); + }, + async deactivate() { + const owned = this.db.get('ownedSubscription'); + if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + }, + }, + async run(event) { + if (!this.polymorfa.authenticate("messageState", this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; } + let data; + try { data = this.polymorfa.envelope(event.body, "messageState"); } catch { this.http.respond({ status: 400 }); return; } + this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) }); + this.http.respond({ status: 200 }); + }, +}; diff --git a/pipedream/sources/numberState/numberState.mjs b/pipedream/sources/numberState/numberState.mjs new file mode 100644 index 0000000..505e5ad --- /dev/null +++ b/pipedream/sources/numberState/numberState.mjs @@ -0,0 +1,25 @@ +// Generated from shared/catalog.cjs. +import polymorfa from '../../polymorfa.app.mjs'; +export default { + key: "polymorfa-numberState", name: "Number State Changed", description: "Triggers when a Number changes state, connects or logs out.", + version: '0.1.0', type: 'source', dedupe: 'unique', + props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + hooks: { + async activate() { + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "numberState", this.subscriptionKey); + this.db.set('ownedSubscription', owned); + }, + async deactivate() { + const owned = this.db.get('ownedSubscription'); + if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + }, + }, + async run(event) { + if (!this.polymorfa.authenticate("numberState", this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; } + let data; + try { data = this.polymorfa.envelope(event.body, "numberState"); } catch { this.http.respond({ status: 400 }); return; } + this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) }); + this.http.respond({ status: 200 }); + }, +}; diff --git a/recipes/README.md b/recipes/README.md new file mode 100644 index 0000000..2187505 --- /dev/null +++ b/recipes/README.md @@ -0,0 +1,39 @@ +# Recipe candidates + +These recipes need provider acceptance before a public template launch. The n8n +JSON files are inactive workflow imports targeting this development node. The +other files are field/step specifications, not claimed provider template URLs. + +| Recipe | Required behavior | Native release dependency | +| --- | --- | --- | +| Order notification | Send a template or text once per saved notification event | Real order trigger, restricted connection, stable key/start-time mapping | +| Inbound handoff | Forward a received message to an authorized destination | Owned authenticated hook, project-wide disclosure acceptance, destination auth, dedupe | +| Reply-aware appointment follow-up | Wait, re-read saved reply state, then send only when due and unanswered | External platform's durable wait and customer-chosen state store; atomic admission/race qualification | +| Custom transformation | Invoke a pinned Function, then map its result | Public Functions contract and regional runtime acceptance; gated on all four platforms | + +For the n8n order workflow, replace the Manual Trigger with an authorized order +source that supplies these string fields: + +```json +{ + "numberId": "YOUR_NUMBER_ID", + "recipient": "YOUR_AUTHORIZED_TEST_RECIPIENT", + "notificationId": "YOUR_SAVED_NOTIFICATION_ID", + "notificationStartedAt": "YOUR_SAVED_ISO_TIMESTAMP", + "notificationText": "YOUR_MESSAGE" +} +``` + +Save `notificationStartedAt` when that logical notification first begins. Do not +replace it with the current clock on every attempt. Missing inputs stop before a +send. For a real send, configure a synthetic test recipient you control. + +The inbound workflow needs its own subscription key, Polymorfa credential and +authorized destination. Its destination starts at `example.invalid`; configure +destination authentication before activation. It sends project-wide message +content to the selected external system. Add persistent dedupe by `id` before +effects. Neither workflow is activated by importing it. + +Make blueprint IDs, Zapier template/share IDs and Pipedream app/component IDs must +come from the real assigned apps. Do not fabricate them. Record the accepted IDs +and native test evidence in the release record, then bind/export the templates. diff --git a/recipes/field-maps.json b/recipes/field-maps.json new file mode 100644 index 0000000..c5ef87d --- /dev/null +++ b/recipes/field-maps.json @@ -0,0 +1,15 @@ +{ + "stage": "native templates pending assigned app IDs and editor acceptance", + "orderNotification": { + "action": "sendText", + "fields": { "session": "order.numberId", "recipientType": "phoneNumber", "recipient": "order.recipient", "text": "order.notificationText", "idempotencyKey": "order-notification:{savedNotificationId}", "writeCreatedAt": "order.savedNotificationStartedAt" }, + "platforms": { + "make": { "module": "sendText", "appId": null, "blueprintId": null }, + "zapier": { "action": "sendText", "appId": null, "templateId": null }, + "pipedream": { "requestedComponentKey": "polymorfa-sendText", "assignedAppId": null, "acceptedComponentId": null } + } + }, + "inboundHandoff": { "trigger": "messageReceived", "identity": "id", "content": "payload", "destination": "customer-authorized CRM/helpdesk", "requiredBeforeEffect": "persistent event-ID dedupe and destination authentication" }, + "appointmentFollowUp": { "steps": ["Save appointment/conversation association in the chosen external store", "Record matching inbound replies by stable event ID", "Wait using the external platform", "Re-read reply/cancellation/expiry state", "Admit the due follow-up using one stable logical-write identity", "Send through the same native action"], "nativeAcceptance": null }, + "functionTransformation": { "stage": "blocked", "reason": "Functions invocation is not in the accepted public contract", "nativeTemplateIds": null } +} diff --git a/recipes/n8n/inbound-handoff.json b/recipes/n8n/inbound-handoff.json new file mode 100644 index 0000000..111acf6 --- /dev/null +++ b/recipes/n8n/inbound-handoff.json @@ -0,0 +1,10 @@ +{ + "name": "Polymorfa inbound handoff (configure before use)", + "active": false, + "nodes": [ + { "id": "inbound-message", "name": "Receive project messages", "type": "n8n-nodes-polymorfa.polymorfaTrigger", "typeVersion": 1, "position": [0, 0], "parameters": { "eventGroup": "messageReceived", "subscriptionKey": "" } }, + { "id": "authorized-destination", "name": "Configure destination auth and persistent dedupe", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [260, 0], "parameters": { "method": "POST", "url": "https://example.invalid/configure-authorized-destination", "sendBody": true, "specifyBody": "json", "jsonBody": "={{JSON.stringify($json)}}", "options": {} } } + ], + "connections": { "Receive project messages": { "main": [[{ "node": "Configure destination auth and persistent dedupe", "type": "main", "index": 0 }]] } }, + "settings": { "executionOrder": "v1" } +} diff --git a/recipes/n8n/order-notification.json b/recipes/n8n/order-notification.json new file mode 100644 index 0000000..708c3c3 --- /dev/null +++ b/recipes/n8n/order-notification.json @@ -0,0 +1,10 @@ +{ + "name": "Polymorfa order notification (configure before use)", + "active": false, + "nodes": [ + { "id": "recipe-input", "name": "Replace with authorized order trigger", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [0, 0], "parameters": {} }, + { "id": "notify-order", "name": "Send order notification", "type": "n8n-nodes-polymorfa.polymorfa", "typeVersion": 1, "position": [260, 0], "parameters": { "operation": "sendText", "session": "={{$json.numberId}}", "recipientType": "phoneNumber", "recipient": "={{$json.recipient}}", "text": "={{$json.notificationText}}", "idempotencyKey": "={{'order-notification:' + $json.notificationId}}", "writeCreatedAt": "={{$json.notificationStartedAt}}" } } + ], + "connections": { "Replace with authorized order trigger": { "main": [[{ "node": "Send order notification", "type": "main", "index": 0 }]] } }, + "settings": { "executionOrder": "v1" } +} diff --git a/releases/0.1.0.json b/releases/0.1.0.json new file mode 100644 index 0000000..25dd09b --- /dev/null +++ b/releases/0.1.0.json @@ -0,0 +1,15 @@ +{ + "version": "0.1.0", + "stage": "development", + "reviewedSource": null, + "apiProductionAcceptance": null, + "n8nRuntimeAcceptance": null, + "publisherOwnership": null, + "publicationAuthorization": null, + "platforms": { + "n8n": { "package": "n8n-nodes-polymorfa", "publishedVersion": null, "verification": null, "listingUrl": null }, + "zapier": { "appId": null, "deployedVersion": null, "directoryStage": null, "listingUrl": null }, + "make": { "appId": null, "deployedVersion": null, "customFunctionsEnabled": false, "approved": false, "listingUrl": null }, + "pipedream": { "appId": null, "registryPr": null, "componentVersions": null, "connectAcceptance": null, "mcpAcceptance": null, "listingUrl": null } + } +} diff --git a/scripts/check-contracts.mjs b/scripts/check-contracts.mjs new file mode 100644 index 0000000..1eb0bef --- /dev/null +++ b/scripts/check-contracts.mjs @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const source = JSON.parse(readFileSync('contracts/source.json')); +const coverage = JSON.parse(readFileSync('contracts/coverage.json')); +const { actions, events } = require('../shared/catalog.cjs'); +assert.equal(source.apiVersion, require('../shared/core.cjs').VERSION); +const operationIds = new Set(); const eventTypes = new Set(); +for (const [kind, pin] of Object.entries(source.specs)) { + const raw = readFileSync(pin.snapshot); + assert.equal(createHash('sha256').update(raw).digest('hex'), pin.sha256, `${kind} snapshot changed without repinning`); + const spec = JSON.parse(raw); + for (const [path, methods] of Object.entries(spec.paths)) for (const [method, operation] of Object.entries(methods)) { + const entry = coverage.operations[operation.operationId]; + assert.ok(entry, `Unexpected operation ${operation.operationId}`); + assert.equal(entry.contract, kind); assert.equal(entry.path, path); assert.equal(entry.method, method.toUpperCase()); + operationIds.add(operation.operationId); + } + for (const schema of Object.values(spec.components.schemas)) for (const value of schema.properties?.event?.enum || []) eventTypes.add(value); + assert.ok(!Object.keys(spec.paths).some(p => /functions|admin|console|quicklink|credentials/i.test(p))); +} +assert.equal(operationIds.size, Object.keys(coverage.operations).length); +for (const a of Object.values(actions)) if (a.operation) assert.ok(operationIds.has(a.operation)); +for (const group of Object.values(events)) for (const type of group.types) assert.ok(eventTypes.has(type), `Missing public event ${type}`); +console.log(`Public contract verified: ${operationIds.size} operations, ${eventTypes.size} event types; SDK pin ${source.sdk.source}.`); diff --git a/scripts/check-upstream.mjs b/scripts/check-upstream.mjs new file mode 100644 index 0000000..d9dab60 --- /dev/null +++ b/scripts/check-upstream.mjs @@ -0,0 +1,52 @@ +import { readFileSync } from 'node:fs'; +import { isDeepStrictEqual } from 'node:util'; +import { createHash } from 'node:crypto'; +const source = JSON.parse(readFileSync('contracts/source.json')); +const coverage = JSON.parse(readFileSync('contracts/coverage.json')); +// Compare SDK contract fragments only. Never import an SDK's extra/private operations. +let mode = source.sdk.source; +if (!process.argv.includes('--pinned')) { + const response = await fetch(`https://api.github.com/repos/${source.sdk.repository}/commits/main`, { redirect: 'error', signal: AbortSignal.timeout(30000) }); + if (!response.ok) throw new Error(`SDK head unavailable: HTTP ${response.status}`); + mode = (await response.json()).sha; + if (!/^[a-f0-9]{40}$/.test(mode)) throw new Error('Invalid SDK source identity.'); +} +const baseline = JSON.parse(readFileSync('contracts/sdk-baseline.json')); +const canonical = value => { + if (Array.isArray(value)) return value.map(canonical); + if (!value || typeof value !== 'object') return value; + return Object.fromEntries(Object.keys(value).sort().map(k => [k, canonical(value[k])])); +}; +const hash = value => createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex'); +const semantic = value => { + if (Array.isArray(value)) return value.map(semantic); + if (!value || typeof value !== 'object') return value; + return Object.fromEntries(Object.entries(value).filter(([k]) => !['description', 'summary', 'example', 'examples', 'x-mint', 'externalDocs'].includes(k)).map(([k, v]) => [k, semantic(v)])); +}; +let failures = 0; +if (mode !== source.sdk.source) { console.error(`SDK main advanced to ${mode}; review its covered behavior and update the integration pin in the same feature cycle.`); failures++; } +let audienceDifferences = 0; +for (const [kind, pin] of Object.entries(source.specs)) { + const url = `https://raw.githubusercontent.com/${source.sdk.repository}/${mode}/${pin.sdkSnapshotPath}`; + const response = await fetch(url, { redirect: 'error', signal: AbortSignal.timeout(30000) }); + if (!response.ok) throw new Error(`SDK snapshot unavailable: HTTP ${response.status}`); + const sdk = await response.json(); const local = JSON.parse(readFileSync(pin.snapshot)); + const seen = new Set(); + const compare = (a, b, label) => { + const key = label.startsWith(kind + ':') ? label.slice(kind.length + 1) : label; + if ((b === undefined ? undefined : hash(b)) !== baseline.fragments[kind][key]) { console.error(`SDK drift from reviewed baseline: ${label}`); failures++; } + if (!isDeepStrictEqual(semantic(a), semantic(b))) audienceDifferences++; + const visit = v => { + if (Array.isArray(v)) return v.forEach(visit); + if (!v || typeof v !== 'object') return; + if (typeof v.$ref === 'string' && !seen.has(v.$ref)) { + seen.add(v.$ref); const [, , section, name] = v.$ref.split('/'); + compare(local.components[section]?.[name], sdk.components?.[section]?.[name], `${kind}:${v.$ref}`); + } + Object.values(v).forEach(visit); + }; visit(a); + }; + for (const item of Object.values(coverage.operations).filter(v => v.contract === kind)) compare(local.paths[item.path][item.method.toLowerCase()], sdk.paths?.[item.path]?.[item.method.toLowerCase()], `${item.method} ${item.path}`); +} +if (failures) { console.error(`${failures} changed fragments require an SDK/API reconciliation. No files were repinned.`); process.exitCode = 1; } +else console.log(`Covered SDK fragments match the reviewed ${source.sdk.repository}@${mode} baseline. ${audienceDifferences} API/SDK audience differences remain explicit; request compatibility is checked separately by check:sdk.`); diff --git a/scripts/generate.mjs b/scripts/generate.mjs new file mode 100644 index 0000000..ffe4b4b --- /dev/null +++ b/scripts/generate.mjs @@ -0,0 +1,113 @@ +import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const { actions, fields, events } = require('../shared/catalog.cjs'); +const core = require('../shared/core.cjs'); +const check = process.argv.includes('--check'); +let drift = false; +function emit(path, value) { + const content = typeof value === 'string' ? value : JSON.stringify(value, null, 2) + '\n'; + if (check) { + if (!existsSync(path) || readFileSync(path, 'utf8') !== content) { console.error(`Generated file differs: ${path}`); drift = true; } + } else { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, content); } +} +for (const target of ['n8n/shared', 'zapier/shared', 'pipedream/common']) { + for (const name of ['catalog', 'core', 'hooks']) { + let text = readFileSync(`shared/${name}.cjs`, 'utf8'); + if (target.startsWith('n8n')) text = text.replaceAll('.cjs', '.js'); + emit(`${target}/${name}.${target.startsWith('n8n') ? 'js' : 'cjs'}`, '// Generated from shared/. Run npm run generate.\n' + text); + } + emit(`${target}/samples.json`, JSON.parse(readFileSync('fixtures/events.json'))); +} +for (const name of ['LICENSE', 'NOTICE.md']) emit(`n8n/${name}`, readFileSync(name, 'utf8')); +const specs = Object.fromEntries(['messaging', 'platform'].map(k => [k, JSON.parse(readFileSync(`contracts/openapi.${k}.json`))])); +function interfaceFields(schema, spec, depth = 0) { + if (!schema || depth > 4) return []; + if (schema.$ref) return interfaceFields(spec.components.schemas[schema.$ref.split('/').at(-1)], spec, depth + 1); + const fields = { ...schema.properties }; + for (const variant of [...(schema.allOf || []), ...(schema.oneOf || []), ...(schema.anyOf || [])]) for (const item of interfaceFields(variant, spec, depth + 1)) if (!fields[item.name]) fields[item.name] = { __interface: item }; + return Object.entries(fields).map(([name, s]) => { + if (s.__interface) return s.__interface; + const resolved = s.$ref ? spec.components.schemas[s.$ref.split('/').at(-1)] : s; + const type = resolved.type === 'array' ? 'array' : resolved.type === 'boolean' ? 'boolean' : ['number', 'integer'].includes(resolved.type) ? 'number' : resolved.type === 'object' || resolved.properties || resolved.oneOf || resolved.anyOf || resolved.allOf ? 'collection' : resolved.format === 'date-time' ? 'date' : 'text'; + const children = type === 'array' ? interfaceFields(resolved.items, spec, depth + 1) : type === 'collection' ? interfaceFields(resolved, spec, depth + 1) : []; + return { name, label: name, type, ...(children.length ? { spec: type === 'array' ? { type: 'collection', spec: children } : children } : {}) }; + }); +} +// Generate native Pipedream components, without a second HTTP client/runtime. +for (const [key, action] of Object.entries(actions)) { + const props = {}; + for (const name of action.fields) { + const f = fields[name]; props[name] = { type: 'string', label: f.label, optional: !f.required, ...(f.help ? { description: f.help } : {}), ...(f.default ? { default: f.default } : {}), ...(f.options ? { options: Object.entries(f.options).map(([value, label]) => ({ value, label })) } : {}) }; + if (['idempotencyKey', 'writeCreatedAt'].includes(name) && key !== 'apiRequest') props[name].optional = false; + } + emit(`pipedream/actions/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(action.label)}, description: ${JSON.stringify(action.description)},\n version: '0.1.0', type: 'action',\n props: { polymorfa, ...${JSON.stringify(props, null, 2)} },\n async run({ $ }) {\n const input = Object.fromEntries(${JSON.stringify(action.fields)}.map(key => [key, this[key]]));\n return this.polymorfa.execute($, ${JSON.stringify(key)}, input);\n },\n};\n`); +} +for (const [key, event] of Object.entries(events)) emit(`pipedream/sources/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(event.label)}, description: ${JSON.stringify(event.description)},\n version: '0.1.0', type: 'source', dedupe: 'unique',\n props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true },\n subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } },\n hooks: {\n async activate() {\n const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, ${JSON.stringify(key)}, this.subscriptionKey);\n this.db.set('ownedSubscription', owned);\n },\n async deactivate() {\n const owned = this.db.get('ownedSubscription');\n if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); }\n },\n },\n async run(event) {\n if (!this.polymorfa.authenticate(${JSON.stringify(key)}, this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; }\n let data;\n try { data = this.polymorfa.envelope(event.body, ${JSON.stringify(key)}); } catch { this.http.respond({ status: 400 }); return; }\n this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) });\n this.http.respond({ status: 200 });\n },\n};\n`); + +// Make custom IML functions reuse the exact shared request/response behavior. +// Every function fits Make's 5,000-character limit. They require provider enablement. +const errorClass = core.IntegrationError.toString(); +const safeCode = "function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; }"; +function imlFunction(name, args, definitions, statement) { + const content = `function ${name}(${args}) {\n${definitions}\n${statement}\n}\n`; + if (content.length > 5000) throw new Error(`${name} exceeds Make's function limit: ${content.length}`); + emit(`make/functions/${name}.js`, content); +} +imlFunction('polymorfaBody', 'action, p', `${errorClass}\nconst MAX_MEDIA_BYTES = ${core.MAX_MEDIA_BYTES};\n${core.required}\n${core.conversation || ''}\n${core.messageBody}`, 'return messageBody(action, p);'); +imlFunction('polymorfaAdvanced', 'p, auth', `${errorClass}\n${core.required}\n${core.object || ''}\nconst ORIGIN = '${core.ORIGIN}';\n${core.advanced || ''}`, 'return advanced(p, auth);'); +imlFunction('polymorfaRequest', 'action, p, auth', `${errorClass}\nconst ORIGIN = '${core.ORIGIN}', VERSION = '${core.VERSION}', MAX_WRITE_AGE = ${23 * 60 * 60 * 1000};\n${core.required}\n${core.segment}\n${core.headers}\n${core.writeHeaders}\n${core.buildRequest.toString().replace('messageBody(action, p)', 'iml.polymorfaBody(action, p)').replace('advanced(p, auth)', 'iml.polymorfaAdvanced(p, auth)')}`, "return buildRequest(action, p, auth, 'make');"); +imlFunction('polymorfaResult', 'status, headers, body', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body });'); +imlFunction('polymorfaSegment', 'value', `${errorClass}\n${core.required}\n${core.segment}`, "return segment(value, 'resource_id');"); +imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst secret = iml.sha256(scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + secret.slice(0, 24), value: secret, key: secret };`); +imlFunction('polymorfaReceive', 'expectedName, expectedValue, group, headers, body', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase());\nif (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false;\nreturn !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload));`); +imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => h.url !== url && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && h.url === url && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (h.url === url || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); +imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || h.url !== ownedUrl || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); + +emit('make/app.json', { name: 'polymorfa', label: 'Polymorfa', version: 1, theme: '#7657ff', language: 'en', description: 'Send messages and receive project events with Polymorfa.' }); +emit('make/base.json', { baseUrl: core.ORIGIN, headers: { Authorization: 'Bearer {{connection.apiKey}}', 'Polymorfa-Version': core.VERSION, 'User-Agent': 'polymorfa-make/0.1.0' }, log: { sanitize: ['request.headers.authorization', 'request.body.headers', 'response.body.data.secret'] }, response: { error: { message: '[{{statusCode}}] Polymorfa {{body.error.code}}' } } }); +emit('make/connections/project/parameters.json', [{ name: 'apiKey', type: 'password', label: 'Project Token', required: true }, { name: 'projectId', type: 'text', label: 'Project ID', required: true }]); +emit('make/connections/project/communication.json', { url: '/platform/sessions', method: 'GET', qs: { projectId: '{{parameters.projectId}}' }, headers: { Authorization: 'Bearer {{parameters.apiKey}}', 'Polymorfa-Version': core.VERSION }, response: { valid: '{{statusCode = 200}}', metadata: { type: 'text', value: '{{parameters.projectId}}' } }, log: { sanitize: ['request.headers.authorization'] } }); +for (const [key, action] of Object.entries(actions)) { + emit(`make/modules/${key}/metadata.json`, { label: key === 'apiRequest' ? 'Make an API call' : action.label, description: key === 'apiRequest' ? 'Performs an arbitrary authorized API call.' : action.description, connection: 'project', type: 'action' }); + emit(`make/modules/${key}/parameters.json`, action.fields.map(name => { + const f = fields[name]; + return { name, type: f.options ? 'select' : 'text', label: f.label, required: !!f.required || (key !== 'apiRequest' && ['idempotencyKey', 'writeCreatedAt'].includes(name)), ...(f.default ? { default: f.default } : {}), ...(f.help ? { help: f.help } : {}), ...(f.options ? { options: Object.entries(f.options).map(([value, label]) => ({ value, label })) } : {}) }; + })); + const r = `polymorfaRequest('${key}'; parameters; connection)`; + emit(`make/modules/${key}/communication.json`, { url: `{{get(${r}; 'url')}}`, method: `{{get(${r}; 'method')}}`, headers: `{{get(${r}; 'headers')}}`, qs: `{{get(${r}; 'query')}}`, body: `{{get(${r}; 'body')}}`, type: 'json', followRedirect: false, response: { valid: '{{polymorfaResult(statusCode; headers; body) != null}}', output: '{{polymorfaResult(statusCode; headers; body)}}' }, log: { sanitize: ['request.headers.authorization', 'request.headers.idempotency-key'] } }); + const spec = key === 'listNumbers' ? specs.platform : specs.messaging; + const response = key === 'listNumbers' ? { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/PlatformSession' } } } } : spec.components.schemas[key === 'listTemplates' ? 'ListProjectTemplatesResponse' : key === 'react' ? 'SendReactionResponse' : 'SendMessageResponse']; + emit(`make/modules/${key}/interface.json`, key === 'apiRequest' ? [{ name: 'data', label: 'Result', type: 'collection' }] : interfaceFields(response, spec)); +} +for (const [key, e] of Object.entries(events)) { + emit(`make/modules/${key}/metadata.json`, { label: e.label, description: e.description, type: 'instant', webhook: key }); + const schemas = Object.values(specs.messaging.components.schemas).filter(s => s.properties?.event?.enum?.some(type => e.types.includes(type))); + const merge = (a, b) => { + const result = [...a]; + for (const item of b) { + const existing = result.find(x => x.name === item.name); + if (!existing) result.push(item); + else if (existing.type === 'collection' && item.type === 'collection') existing.spec = merge(existing.spec || [], item.spec || []); + } + return result; + }; + emit(`make/modules/${key}/interface.json`, schemas.reduce((all, schema) => merge(all, interfaceFields(schema, specs.messaging)), [])); + emit(`make/webhooks/${key}/parameters.json`, [{ name: 'subscriptionKey', label: 'Subscription Key', type: 'text', required: true, help: 'Unique stable label for this webhook. Events are project-wide.' }]); + emit(`make/webhooks/${key}/metadata.json`, { type: 'dedicated', connection: 'project', label: e.label }); + const marker = `polymorfaHook(connection; '${key}'; parameters.subscriptionKey)`; + const markerField = field => `{{get(${marker}; '${field}')}}`; + emit(`make/webhooks/${key}/attach.json`, [ + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}` }, data: { externalHookId: '{{temp.ownedId}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, + { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `integration-create-${markerField('key')}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { data: { externalHookId: '{{body.data.webhook.id}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, + ]); + emit(`make/webhooks/${key}/detach.json`, [ + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'GET', response: { temp: { gone: '{{statusCode = 404}}' }, valid: `{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}'))}}` } }, + { condition: '{{!temp.gone}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'DELETE', headers: { 'Idempotency-Key': 'integration-delete-{{webhook.externalHookId}}' } }, + ]); + const valid = `polymorfaReceive(parameters.receiverName; parameters.receiverValue; '${key}'; headers; body)`; + emit(`make/webhooks/${key}/communication.json`, { condition: `{{${valid}}}`, respond: { status: `{{if(${valid}; 200; 401)}}`, type: 'json', body: {} }, output: { id: '{{body.id}}', session: '{{body.session}}', timestamp: '{{body.timestamp}}', event: '{{body.event}}', payload: '{{body.payload}}' } }); +} +if (drift) process.exitCode = 1; +else console.log(check ? 'Generated adapter files are current.' : 'Generated native adapter files.'); diff --git a/scripts/openapi-schema.cjs b/scripts/openapi-schema.cjs new file mode 100644 index 0000000..59d2ff5 --- /dev/null +++ b/scripts/openapi-schema.cjs @@ -0,0 +1,15 @@ +'use strict'; +const assert = require('node:assert/strict'); +// Convert OpenAPI 3.0's draft-04 exclusive bounds without changing constraints. +module.exports = function openApi3(value) { + if (Array.isArray(value)) return value.map(openApi3); + if (!value || typeof value !== 'object') return value; + const out = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, openApi3(v)])); + for (const [exclusive, bound] of [['exclusiveMinimum', 'minimum'], ['exclusiveMaximum', 'maximum']]) { + if (typeof out[exclusive] === 'boolean') { + const enabled = out[exclusive]; delete out[exclusive]; + if (enabled) { assert.equal(typeof out[bound], 'number'); out[exclusive] = out[bound]; delete out[bound]; } + } + } + return out; +}; diff --git a/scripts/release-gate.mjs b/scripts/release-gate.mjs new file mode 100644 index 0000000..8a01ea7 --- /dev/null +++ b/scripts/release-gate.mjs @@ -0,0 +1,11 @@ +import { readFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import assert from 'node:assert/strict'; +const pkg = JSON.parse(readFileSync('n8n/package.json')); +assert.equal(process.env.RELEASE_VERSION, pkg.version, 'Dispatch version must match the reviewed package.'); +const receipt = JSON.parse(readFileSync(`releases/${pkg.version}.json`)); +for (const key of ['reviewedSource', 'apiProductionAcceptance', 'n8nRuntimeAcceptance', 'publisherOwnership', 'publicationAuthorization']) assert.ok(typeof receipt[key] === 'string' && receipt[key].length > 10, `Missing ${key} receipt. Source completion is not publication authority.`); +assert.match(receipt.reviewedSource, /^[a-f0-9]{40}$/); +// Receipt-only changes may follow the reviewed candidate; executable source must match. +execFileSync('git', ['diff', '--exit-code', receipt.reviewedSource, 'HEAD', '--', 'shared', 'n8n', 'contracts', 'scripts', 'fixtures', 'package.json', 'package-lock.json', '.github/workflows/publish-n8n.yml'], { stdio: 'inherit' }); +console.log('Required publication receipts are recorded. Protected environment approval and npm trusted publisher configuration remain required.'); diff --git a/scripts/update-sdk-baseline.mjs b/scripts/update-sdk-baseline.mjs new file mode 100644 index 0000000..f14b521 --- /dev/null +++ b/scripts/update-sdk-baseline.mjs @@ -0,0 +1,34 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import assert from 'node:assert/strict'; +const source = JSON.parse(readFileSync('contracts/source.json')); +assert.equal(process.argv[2], source.sdk.source, 'Pass the exact reviewed SDK source SHA. Repinning is a deliberate PR change.'); +const coverage = JSON.parse(readFileSync('contracts/coverage.json')); +const canonical = v => Array.isArray(v) ? v.map(canonical) : v && typeof v === 'object' ? Object.fromEntries(Object.keys(v).sort().map(k => [k, canonical(v[k])])) : v; +const hash = v => createHash('sha256').update(JSON.stringify(canonical(v))).digest('hex'); +const fragments = {}; +for (const [kind, pin] of Object.entries(source.specs)) { + const response = await fetch(`https://raw.githubusercontent.com/${source.sdk.repository}/${source.sdk.source}/${pin.sdkSnapshotPath}`, { redirect: 'error', signal: AbortSignal.timeout(30000) }); + assert.ok(response.ok, `SDK ${kind} HTTP ${response.status}`); + const sdk = await response.json(); const local = JSON.parse(readFileSync(pin.snapshot)); + const entries = {}; const seen = new Set(); + const visit = v => { + if (Array.isArray(v)) return v.forEach(visit); + if (!v || typeof v !== 'object') return; + if (typeof v.$ref === 'string' && !seen.has(v.$ref)) { + seen.add(v.$ref); const [, , section, name] = v.$ref.split('/'); + const part = sdk.components?.[section]?.[name]; + if (part) { entries[v.$ref] = hash(part); visit(part); } + } + Object.values(v).forEach(visit); + }; + for (const operation of Object.values(coverage.operations).filter(o => o.contract === kind)) { + const op = sdk.paths?.[operation.path]?.[operation.method.toLowerCase()]; + assert.ok(op, `Missing SDK operation ${operation.method} ${operation.path}`); + entries[`${operation.method} ${operation.path}`] = hash(op); visit(op); + visit(local.paths[operation.path][operation.method.toLowerCase()]); + } + fragments[kind] = entries; +} +writeFileSync('contracts/sdk-baseline.json', JSON.stringify({ source: source.sdk.source, note: 'Fingerprints of reviewed public SDK fragments. The SDK audience differs from the public API projection; no extra operation is imported. Changes require reconciliation, not automatic repinning.', fragments }, null, 2) + '\n'); +console.log(`Updated SDK fragment fingerprints from immutable source ${source.sdk.source}; review the diff before committing.`); diff --git a/scripts/verify-sdk.mjs b/scripts/verify-sdk.mjs new file mode 100644 index 0000000..68165b0 --- /dev/null +++ b/scripts/verify-sdk.mjs @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import assert from 'node:assert/strict'; +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; +const require = createRequire(import.meta.url); +const openApi3 = require('./openapi-schema.cjs'); +const { buildRequest } = require('../shared/core.cjs'); +const source = JSON.parse(readFileSync('contracts/source.json')); +const ajv = new Ajv({ strict: false, allErrors: true }); addFormats(ajv); +for (const kind of ['messaging', 'platform']) { + const response = await fetch(`https://raw.githubusercontent.com/${source.sdk.repository}/${source.sdk.source}/${source.specs[kind].sdkSnapshotPath}`, { redirect: 'error', signal: AbortSignal.timeout(30000) }); + assert.ok(response.ok, `SDK ${kind} snapshot HTTP ${response.status}`); + ajv.addSchema(openApi3(await response.json()), kind); +} +const auth = { apiKey: 'synthetic-project-token', projectId: '11111111-1111-4111-8111-111111111111' }; +const input = { session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'Synthetic', idempotencyKey: 'synthetic-write', writeCreatedAt: new Date().toISOString() }; +const cases = [['sendText', {}], ['sendTemplate', { templateName: 'order_ready', language: 'en', components: '[]' }], ['react', { messageId: '739182640518204', reaction: '' }], ...['image', 'video', 'file', 'voice'].flatMap(mediaType => ['url', 'base64'].map(mediaSource => ['sendMedia', { mediaType, mediaSource, media: mediaSource === 'url' ? 'https://example.com/media' : 'AAECAw==' }]))]; +for (const [action, extra] of cases) { + const request = buildRequest(action, { ...input, ...extra }, auth); + const validate = ajv.compile({ $ref: `messaging#/components/schemas/${action === 'react' ? 'ReactRequest' : 'SendMessageRequest'}` }); + assert.ok(validate(request.body), `${action}: ${JSON.stringify(validate.errors)}`); +} +const webhook = { url: 'https://example.com/synthetic', eventTypes: ['message.received'], enabled: true, format: 'native', headers: [{ name: 'X-Polymorfa-Integration-test', value: 'synthetic-receiver-secret' }] }; +const validate = ajv.compile({ $ref: 'platform#/components/schemas/CreateProjectWebhookInput' }); +assert.ok(validate(webhook), JSON.stringify(validate.errors)); +console.log(`${cases.length} action request cases and webhook registration match SDK ${source.sdk.source}. No SDK-only field or operation is exposed.`); diff --git a/shared/catalog.cjs b/shared/catalog.cjs new file mode 100644 index 0000000..408d9c4 --- /dev/null +++ b/shared/catalog.cjs @@ -0,0 +1,42 @@ +'use strict'; + +const actions = { + sendText: { label: 'Send Text', description: 'Sends a text message or replies to a message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'text', 'quotedMessageId', 'idempotencyKey', 'writeCreatedAt'] }, + sendTemplate: { label: 'Send Template', description: 'Sends a template with its approved name and language.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'templateName', 'language', 'components', 'idempotencyKey', 'writeCreatedAt'] }, + sendMedia: { label: 'Send Media', description: 'Sends an image, video, file or voice message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'mediaType', 'mediaSource', 'media', 'mimeType', 'caption', 'filename', 'idempotencyKey', 'writeCreatedAt'] }, + react: { label: 'React to Message', description: 'Adds or removes a reaction on a message.', operation: 'sendReaction', fields: ['session', 'recipientType', 'recipient', 'messageId', 'reaction', 'idempotencyKey', 'writeCreatedAt'] }, + listNumbers: { label: 'List Numbers', description: 'Lists Numbers visible to the connected project.', operation: 'listPlatformSessions', fields: [] }, + listTemplates: { label: 'List Templates', description: 'Lists message templates for a project.', operation: 'listProjectTemplates', fields: ['projectSlug'] }, + apiRequest: { label: 'Make an API Call', description: 'Performs an authorized call to a supported public Polymorfa operation.', operation: null, fields: ['method', 'path', 'requestBody', 'idempotencyKey', 'writeCreatedAt'] }, +}; +const fields = { + session: { label: 'Number', required: true, help: 'The exact Number identifier from Polymorfa. Keep it as a string.' }, + recipientType: { label: 'Recipient Type', required: true, options: { phoneNumber: 'Phone Number', id: 'Conversation ID', bsuid: 'Business-scoped User ID' }, default: 'phoneNumber' }, + recipient: { label: 'Recipient', required: true, help: 'Use an international phone number including +, a conversation ID, or a business-scoped user ID.' }, + text: { label: 'Text', required: true }, + quotedMessageId: { label: 'Reply to Message ID', help: 'Use the Polymorfa message ID, not a WhatsApp transport reference.' }, + templateName: { label: 'Template Name', required: true }, + language: { label: 'Template Language', required: true, help: 'The exact language code for the selected template.' }, + components: { label: 'Template Components', json: true, default: '[]', help: 'JSON array matching the template definition.' }, + mediaType: { label: 'Media Type', required: true, options: { image: 'Image', video: 'Video', file: 'File', voice: 'Voice' }, default: 'image' }, + mediaSource: { label: 'Media Source', required: true, options: { url: 'HTTPS URL', base64: 'Base64 Bytes' }, default: 'url' }, + media: { label: 'Media', required: true, help: 'HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower.' }, + mimeType: { label: 'MIME Type' }, + caption: { label: 'Caption' }, + filename: { label: 'Filename', help: 'Used for file messages.' }, + messageId: { label: 'Message ID', required: true }, + reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, + writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, + projectSlug: { label: 'Project Slug', required: true }, + method: { label: 'Method', required: true, options: { GET: 'GET', POST: 'POST' }, default: 'GET' }, + path: { label: 'Path', required: true, help: 'A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused.' }, + requestBody: { label: 'Request Body', json: true, default: '{}', help: 'JSON object. Authentication and version headers are supplied by the connection.' }, +}; +const events = { + messageReceived: { label: 'New Message', description: 'Triggers when a message is received.', types: ['message.received'] }, + messageState: { label: 'Message State Changed', description: 'Triggers when a message is sent, acknowledged, failed or updated.', types: ['message.sent', 'message.ack', 'message.failed', 'message.update'] }, + numberState: { label: 'Number State Changed', description: 'Triggers when a Number changes state, connects or logs out.', types: ['session.status', 'session.connected', 'session.logged_out'] }, + callEnded: { label: 'Call Ended', description: 'Triggers when a call ends.', types: ['call.ended'] }, +}; +module.exports = { actions, fields, events }; diff --git a/shared/core.cjs b/shared/core.cjs new file mode 100644 index 0000000..44d9f85 --- /dev/null +++ b/shared/core.cjs @@ -0,0 +1,128 @@ +'use strict'; + +const ORIGIN = 'https://api.polymorfa.com'; +const VERSION = '2026-09-22'; +const MAX_MEDIA_BYTES = 4 * 1024 * 1024; +const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +function object(value, name) { + let parsed = value; + try { if (typeof value === 'string') parsed = JSON.parse(value); } catch { throw new IntegrationError(`invalid_${name}_json`); } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new IntegrationError(`invalid_${name}`); + return parsed; +} +function conversation(p) { + if (!['phoneNumber', 'id', 'bsuid'].includes(p.recipientType)) throw new IntegrationError('invalid_recipient_type'); + required(p.recipient, 'recipient'); + if (p.recipientType === 'phoneNumber' && !/^\+[1-9]\d{1,14}$/.test(p.recipient)) throw new IntegrationError('invalid_phone_number'); + if (p.recipientType === 'id' && !/^[1-9]\d{0,18}$/.test(p.recipient)) throw new IntegrationError('invalid_conversation_id'); + if (p.recipientType === 'bsuid' && !/^[A-Z]{2}\.[A-Za-z0-9]{1,128}$/.test(p.recipient)) throw new IntegrationError('invalid_bsuid'); + return { [p.recipientType]: p.recipient }; +} +function messageBody(action, p) { + const body = { conversation: conversation(p) }; + if (action === 'sendText') { + body.content = { text: required(p.text, 'text') }; + if (p.quotedMessageId) { + if (!/^[1-9]\d{0,18}$/.test(required(p.quotedMessageId, 'quoted_message_id'))) throw new IntegrationError('invalid_quoted_message_id'); + body.quotedMessage = { id: p.quotedMessageId }; + } + } else if (action === 'sendTemplate') { + let components; + try { components = typeof p.components === 'string' ? JSON.parse(p.components || '[]') : (p.components ?? []); } catch { throw new IntegrationError('invalid_components_json'); } + if (!Array.isArray(components) || components.length > 1000) throw new IntegrationError('invalid_components'); + body.content = { template: { name: required(p.templateName, 'template_name'), language: required(p.language, 'language'), components } }; + if (p.language.length < 2) throw new IntegrationError('invalid_language'); + } else if (action === 'sendMedia') { + if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); + required(p.media, 'media'); + if (p.mediaSource === 'url') { + let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } + if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + throw new IntegrationError('invalid_or_oversized_media'); + } + const media = { [p.mediaSource]: p.media }; + for (const key of ['mimeType', 'caption']) if (p[key]) media[key] = required(p[key], key); + if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); + body.content = { [p.mediaType]: media }; + } else if (action === 'react') { + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = p.reaction; + } else throw new IntegrationError('unknown_action'); + return body; +} +function writeHeaders(p, now = Date.now()) { + const key = required(p.idempotencyKey, 'idempotency_key'); + if (Buffer.byteLength(key, 'utf8') > 255 || /[\x00-\x1f\x7f]/.test(key)) throw new IntegrationError('invalid_idempotency_key'); + const created = Date.parse(required(p.writeCreatedAt, 'write_started_at')); + if (!Number.isFinite(created) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(p.writeCreatedAt) || created > now + 60000 || now - created >= MAX_WRITE_AGE) throw new IntegrationError('write_requires_reconciliation'); + return { 'Idempotency-Key': key }; +} +function headers(auth, provider) { + const token = required(auth.apiKey, 'project_token'); + if (/[\r\n]/.test(token)) throw new IntegrationError('invalid_project_token'); + segment(auth.projectId, 'project_id'); + return { Authorization: `Bearer ${token}`, 'Polymorfa-Version': VERSION, 'Content-Type': 'application/json', 'User-Agent': `polymorfa-${provider}/0.1.0` }; +} +function advanced(p, auth) { + const path = required(p.path, 'path'); + if (!path.startsWith('/') || path.startsWith('//') || /[%\\?#\s]/.test(path) || path.split('/').some(x => x === '.' || x === '..')) throw new IntegrationError('unsupported_path'); + const method = p.method; + const allowed = (method === 'GET' && (path === '/platform/sessions' || /^\/messaging\/projects\/[A-Za-z0-9_-]+\/templates(?:\/[A-Za-z0-9_-]+)?$/.test(path))) || + (method === 'POST' && /^\/messaging\/[A-Za-z0-9_-]+\/messages\/(send|react)$/.test(path)); + if (!allowed) throw new IntegrationError('unsupported_operation'); + const body = method === 'POST' ? object(p.requestBody, 'request_body') : undefined; + if (body) { + const keys = path.endsWith('/react') ? ['conversation', 'id', 'reaction'] : ['conversation', 'isForwarded', 'mentions', 'quotedMessage', 'content']; + if (Object.keys(body).some(k => !keys.includes(k))) throw new IntegrationError('unsupported_request_field'); + } + return { method, url: ORIGIN + path, ...(body ? { body } : {}), ...(path === '/platform/sessions' ? { query: { projectId: auth.projectId } } : {}) }; +} +function buildRequest(action, p, auth, provider = 'integration', now) { + let r; + if (['sendText', 'sendTemplate', 'sendMedia', 'react'].includes(action)) r = { method: 'POST', url: `${ORIGIN}/messaging/${segment(p.session, 'number')}/messages/${action === 'react' ? 'react' : 'send'}`, body: messageBody(action, p) }; + else if (action === 'listNumbers') r = { method: 'GET', url: ORIGIN + '/platform/sessions', query: { projectId: auth.projectId } }; + else if (action === 'listTemplates') r = { method: 'GET', url: `${ORIGIN}/messaging/projects/${segment(p.projectSlug, 'project_slug')}/templates` }; + else if (action === 'apiRequest') r = advanced(p, auth); + else throw new IntegrationError('unknown_action'); + r.headers = { ...headers(auth, provider), ...(r.method === 'POST' ? writeHeaders(p, now) : {}) }; + return r; +} +function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; } +function parseResponse(response) { + const status = response.status; + let body = response.body; + try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + if (status < 200 || status >= 300) { + const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); + const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); + const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + } + if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); + return body; +} +async function execute(action, p, auth, transport, provider, now) { + const request = buildRequest(action, p, auth, provider, now); + let response; + try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } + return parseResponse(response); +} +module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/shared/hooks.cjs b/shared/hooks.cjs new file mode 100644 index 0000000..eafa6cd --- /dev/null +++ b/shared/hooks.cjs @@ -0,0 +1,91 @@ +'use strict'; + +const { createHmac, timingSafeEqual } = require('node:crypto'); +const { events } = require('./catalog.cjs'); +const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); +function identity(auth, group, subscriptionKey) { + if (!events[group]) throw new IntegrationError('invalid_event_group'); + required(subscriptionKey, 'subscription_key'); + if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); + const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); + const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; +} +function callback(url) { + let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } + if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); + return url; +} +function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { + const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); + const types = events[group].types; + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && + hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); +} +async function call(auth, transport, provider, method, path, body, key, query) { + let response; + try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } + catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } + return parseResponse(response); +} +async function find(auth, url, group, subscriptionKey, transport, provider) { + let cursor; const seen = new Set(); const matches = []; + for (let i = 0; i < 100; i++) { + const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); + if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); + const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); + if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + for (const hook of r.data) { + const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); + if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); + } + if (!r.page?.hasMore) { + if (matches.length > 1) throw new IntegrationError('duplicate_owned_subscriptions'); + return matches[0]; + } + if (typeof r.page.nextCursor !== 'string' || seen.has(r.page.nextCursor)) throw new IntegrationError('invalid_subscription_pagination'); + cursor = r.page.nextCursor; seen.add(cursor); + } + throw new IntegrationError('subscription_inventory_requires_reconciliation'); +} +async function subscribe(auth, url, group, subscriptionKey, transport, provider) { + callback(url); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const existing = await find(auth, url, group, subscriptionKey, transport, provider); + if (existing) return record(existing); + const marker = identity(auth, group, subscriptionKey); + // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + try { + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + return record(r.data.webhook); + } catch { + // A lost response may follow a successful create. Reconcile; never blindly create again. + const recovered = await find(auth, url, group, subscriptionKey, transport, provider); + if (recovered) return record(recovered); + throw new IntegrationError('subscription_requires_reconciliation'); + } +} +async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { + const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; + let hook; + try { hook = (await call(auth, transport, provider, 'GET', path)).data; } + catch (error) { if (error.status === 404) return; throw error; } + // Disabled hooks remain owned and may be deleted; all other ownership checks still apply. + if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); + await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); +} +function authenticate(auth, group, subscriptionKey, incomingHeaders) { + const expected = identity(auth, group, subscriptionKey); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; + return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); +} +function envelope(body, group) { + if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); + // Copy the defined envelope only. Never export arbitrary top-level transport fields. + return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; +} +module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/tests/core.test.cjs b/tests/core.test.cjs new file mode 100644 index 0000000..ced77c5 --- /dev/null +++ b/tests/core.test.cjs @@ -0,0 +1,78 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const core = require('../shared/core.cjs'); +const { actions } = require('../shared/catalog.cjs'); +const spec = require('../contracts/openapi.messaging.json'); +const ajv = new Ajv({ strict: false, allErrors: true }); addFormats(ajv); +ajv.addSchema(spec, 'messaging'); +const now = Date.parse('2026-11-01T12:00:00Z'); +const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; +const input = { session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'An order is ready.', idempotencyKey: 'order-123-notification-1', writeCreatedAt: '2026-11-01T11:59:00Z' }; +test('text/reply preserves opaque IDs, version, credential and logical-write identity', () => { + const r = core.buildRequest('sendText', { ...input, quotedMessageId: '9007199254740993124' }, auth, 'test', now); + assert.equal(r.url, 'https://api.polymorfa.com/messaging/739182640518203/messages/send'); + assert.deepEqual(r.body, { conversation: { id: '9007199254740993123' }, content: { text: input.text }, quotedMessage: { id: '9007199254740993124' } }); + assert.equal(r.headers['Idempotency-Key'], input.idempotencyKey); assert.equal(r.headers['Polymorfa-Version'], '2026-09-22'); + assert.equal(r.headers.Authorization, `Bearer ${auth.apiKey}`); +}); +const sendCases = [ + ['sendText', {}], ['sendTemplate', { templateName: 'order_ready', language: 'en', components: '[{"type":"body","parameters":[{"type":"text","text":"123"}]}]' }], + ...['image', 'video', 'file', 'voice'].flatMap(mediaType => ['url', 'base64'].map(mediaSource => ['sendMedia', { mediaType, mediaSource, media: mediaSource === 'url' ? 'https://example.com/synthetic.png' : 'AAECAw==', mimeType: 'image/png', filename: 'example.png' }])), + ['react', { messageId: '9007199254740993124', reaction: '' }], +]; +for (const [action, extra] of sendCases) test(`${action} ${extra.mediaType || ''} ${extra.mediaSource || ''} matches the public request schema`, () => { + const r = core.buildRequest(action, { ...input, ...extra }, auth, 'test', now); + const schema = action === 'react' ? 'ReactRequest' : 'SendMessageRequest'; + const validate = ajv.compile({ $ref: `messaging#/components/schemas/${schema}` }); + assert.ok(validate(r.body), JSON.stringify(validate.errors)); +}); +for (const key of [undefined, '', 'a'.repeat(256), 'é'.repeat(128), 'bad\r\nheader']) test(`invalid write identity is refused before transport: ${String(key).slice(0, 12)}`, async () => { + let called = false; + await assert.rejects(core.execute('sendText', { ...input, idempotencyKey: key }, auth, () => { called = true; }, 'test', now)); + assert.equal(called, false); +}); +for (const created of [undefined, 'not a date', '2026-10-31T12:00:00Z', '2026-11-01T12:02:00Z']) test(`uncertain age refuses a new write: ${created}`, () => { + assert.throws(() => core.buildRequest('sendText', { ...input, writeCreatedAt: created }, auth, 'test', now)); +}); +for (const [method, path] of [ + ['POST', 'https://attacker.example/'], ['GET', '//attacker.example/'], ['GET', '/platform/sessions?projectId=other'], + ['GET', '/platform/../admin'], ['GET', '/platform/%2e%2e/credentials'], ['GET', '/platform\\sessions'], + ['GET', '/platform/projects/project/credentials'], ['POST', '/platform/projects/project/webhooks'], + ['POST', '/platform/functions/invoke'], ['DELETE', '/messaging/number/messages/send'], +]) test(`advanced request refuses ${method} ${path}`, () => assert.throws(() => core.buildRequest('apiRequest', { method, path, requestBody: '{}' }, auth, 'test', now))); +test('advanced allowed read scopes the Number list to the connected project', () => { + const r = core.buildRequest('apiRequest', { method: 'GET', path: '/platform/sessions' }, auth); + assert.deepEqual(r.query, { projectId: auth.projectId }); +}); +test('advanced sends cannot add SDK-only transport fields to the public request', () => { + assert.throws(() => core.buildRequest('apiRequest', { ...input, method: 'POST', path: '/messaging/739182640518203/messages/send', requestBody: JSON.stringify({ conversation: { id: input.recipient }, content: { text: 'Synthetic' }, transport: 'linked_devices' }) }, auth, 'test', now), e => e.code === 'unsupported_request_field'); +}); +for (const code of ['idempotency_completed', 'idempotency_in_progress', 'idempotency_conflict', 'idempotency_outcome_unknown', 'feature_unavailable', 'billing_credit_required', 'unsupported_for_connection']) test(`API ${code} is a failure with no fabricated ID or replacement write`, async () => { + const calls = []; + await assert.rejects(core.execute('sendText', input, auth, async r => { calls.push(r); return { status: 409, headers: { 'Retry-After': '7' }, body: { error: { code, message: auth.apiKey } } }; }, 'test', now), e => e.code === code && !e.message.includes(auth.apiKey) && e.retryAfter === 7); + assert.equal(calls.length, 1); +}); +test('lost write response does not resend or leak transport details', async () => { + let sends = 0; + await assert.rejects(core.execute('sendText', input, auth, async () => { sends++; throw new Error(auth.apiKey); }, 'test', now), e => e.code === 'outcome_unknown' && !e.message.includes(auth.apiKey)); + assert.equal(sends, 1); +}); +test('rate limits, redirects and server errors are surfaced without dispatch loops', () => { + for (const status of [302, 429, 500, 504]) assert.throws(() => core.parseResponse({ status, headers: { 'retry-after': '30' }, body: { error: { code: 'rate_limited' } } }), e => e.status === status && e.retryAfter === 30); +}); +test('accepted async response remains an admission receipt', async () => { + const response = { success: true, data: { requestId: '11111111-1111-4111-8111-111111111111' } }; + assert.deepEqual(await core.execute('sendText', input, auth, async () => ({ status: 202, body: response }), 'test', now), response); +}); +for (const p of [ + { mediaSource: 'url', media: 'http://example.com/media' }, { mediaSource: 'url', media: 'https://user:secret@example.com/file' }, + { mediaSource: 'base64', media: 'data:image/png;base64,AA==' }, { mediaSource: 'base64', media: '====' }, + { mediaSource: 'base64', media: Buffer.alloc(core.MAX_MEDIA_BYTES + 1).toString('base64') }, +]) test(`invalid media ${p.mediaSource} is refused before a send`, () => assert.throws(() => core.buildRequest('sendMedia', { ...input, mediaType: 'file', ...p }, auth, 'test', now))); +test('public surface excludes unlaunched Functions and message-operation lookups', () => { + assert.ok(!('runFunction' in actions)); assert.ok(!('getMessageOperation' in actions)); +}); +module.exports = { auth, input, now }; diff --git a/tests/hooks.test.cjs b/tests/hooks.test.cjs new file mode 100644 index 0000000..a37453d --- /dev/null +++ b/tests/hooks.test.cjs @@ -0,0 +1,91 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const hooks = require('../shared/hooks.cjs'); +const Ajv = require('ajv'); const addFormats = require('ajv-formats'); +const { events } = require('../shared/catalog.cjs'); +const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; +const url = 'https://example.com/synthetic-callback'; +const key = 'synthetic-trigger-1'; +function server() { + const rows = []; const calls = []; let lost = false; + return { rows, calls, loseNextCreate() { lost = true; }, async request(r) { + calls.push(r); + assert.equal(new URL(r.url).origin, 'https://api.polymorfa.com'); + assert.equal(r.headers.Authorization, `Bearer ${auth.apiKey}`); + if (r.method === 'GET' && r.url.endsWith('/webhooks')) return { status: 200, body: { data: rows, page: { hasMore: false, nextCursor: null } } }; + if (r.method === 'POST') { + const row = { ...r.body, owner: 'project', projectId: auth.projectId, id: '22222222-2222-4222-8222-222222222222', headers: r.body.headers.map(h => ({ name: h.name })) }; rows.push(row); + if (lost) { lost = false; throw new Error('Response lost after accepted create'); } + return { status: 200, body: { data: { webhook: row, secret: 'SYNTHETIC-ONE-TIME-SIGNING-SECRET' } } }; + } + const row = rows.find(row => r.url.endsWith('/' + row.id)); + if (!row) return { status: 404, body: { error: { code: 'not_found' } } }; + if (r.method === 'DELETE') { rows.splice(rows.indexOf(row), 1); return { status: 200, body: { data: {} } }; } + return { status: 200, body: { data: row } }; + } }; +} +for (const group of Object.keys(events)) test(`${group}: activation, reactivation, authentication and owned cleanup`, async () => { + const s = server(); const transport = s.request.bind(s); + const first = await hooks.subscribe(auth, url, group, key, transport, 'test'); + assert.deepEqual(Object.keys(first).sort(), ['group', 'id', 'markerName', 'subscriptionKey', 'url']); + assert.ok(!JSON.stringify(first).includes('SYNTHETIC-ONE-TIME-SIGNING-SECRET')); + assert.deepEqual(await hooks.subscribe(auth, url, group, key, transport, 'test'), first); + assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); + const marker = hooks.identity(auth, group, key); + assert.ok(hooks.authenticate(auth, group, key, { [marker.name.toLowerCase()]: marker.value })); + assert.ok(!hooks.authenticate(auth, group, 'another-trigger', { [marker.name]: marker.value })); + assert.ok(!hooks.authenticate({ ...auth, apiKey: 'rotated-synthetic-key' }, group, key, { [marker.name]: marker.value })); + for (const bad of [{}, { [marker.name]: ['a', 'b'] }, { [marker.name]: 'not-a-secret' }, { [marker.name]: marker.value + 'x' }]) assert.ok(!hooks.authenticate(auth, group, key, bad)); + await hooks.unsubscribe(auth, first.id, url, group, key, transport, 'test'); + assert.equal(s.rows.length, 0); +}); +test('an ambiguous accepted create is recovered without another create', async () => { + const s = server(); s.loseNextCreate(); + const result = await hooks.subscribe(auth, url, 'messageReceived', key, s.request.bind(s), 'test'); + assert.ok(result.id); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); +}); +test('an operator-disabled hook is preserved instead of silently enabling or duplicating it', async () => { + const s = server(); const t = s.request.bind(s); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); s.rows[0].enabled = false; + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'), e => e.code === 'subscription_changed_requires_reconciliation'); + assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); +}); +test('a different callback cannot reuse the same active subscription identity', async () => { + const s = server(); const t = s.request.bind(s); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); + await assert.rejects(hooks.subscribe(auth, 'https://other.example/hook', 'messageReceived', key, t, 'test'), e => e.code === 'subscription_key_already_in_use'); + assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); +}); +test('owned lookup follows cursor pages and refuses looping cursors', async () => { + const marker = hooks.identity(auth, 'messageReceived', key); const calls = []; + const hook = { id: 'owned', projectId: auth.projectId, owner: 'project', enabled: true, format: 'native', url, headers: [{ name: marker.name }], eventTypes: ['message.received'] }; + const found = await hooks.find(auth, url, 'messageReceived', key, async r => { + calls.push(r); return { status: 200, body: r.query.cursor ? { data: [hook], page: { hasMore: false } } : { data: [], page: { hasMore: true, nextCursor: 'next' } } }; + }, 'test'); + assert.equal(found.id, 'owned'); assert.equal(calls.length, 2); assert.equal(calls[1].query.cursor, 'next'); + await assert.rejects(hooks.find(auth, url, 'messageReceived', key, async () => ({ status: 200, body: { data: [], page: { hasMore: true, nextCursor: 'loop' } } }), 'test'), e => e.code === 'invalid_subscription_pagination'); +}); +test('cleanup refuses another subscription and never falls back to broader credentials', async () => { + const s = server(); const t = s.request.bind(s); const owned = await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); + await assert.rejects(hooks.unsubscribe(auth, owned.id, 'https://another.example/hook', 'messageReceived', key, t, 'test'), e => e.code === 'subscription_ownership_mismatch'); + assert.equal(s.calls.filter(r => r.method === 'DELETE').length, 0); +}); +test('only the selected public envelope is emitted and event IDs remain stable', () => { + const samples = require('../fixtures/events.json'); + for (const [group, body] of Object.entries(samples)) { + const event = hooks.envelope({ ...body, headers: { Authorization: 'must-not-escape' } }, group); + assert.equal(event.id, body.id); assert.ok(!('headers' in event)); + assert.throws(() => hooks.envelope({ ...body, event: 'session.qr' }, group)); + assert.throws(() => hooks.envelope({ ...body, session: 739182640518203 }, group)); + } +}); +test('editor samples match their public event schemas', () => { + const ajv = new Ajv({ strict: false, allErrors: true }); addFormats(ajv); + ajv.addSchema(require('../scripts/openapi-schema.cjs')(require('../contracts/openapi.messaging.json')), 'messaging'); + const names = { messageReceived: 'MessageReceivedEvent', messageState: 'MessageSentEvent', numberState: 'SessionStatusEvent', callEnded: 'CallEndedEvent' }; + for (const [group, sample] of Object.entries(require('../fixtures/events.json'))) { + const validate = ajv.compile({ $ref: `messaging#/components/schemas/${names[group]}` }); + assert.ok(validate(sample), `${group}: ${JSON.stringify(validate.errors)}`); + } +}); diff --git a/tests/make.test.cjs b/tests/make.test.cjs new file mode 100644 index 0000000..37a00f2 --- /dev/null +++ b/tests/make.test.cjs @@ -0,0 +1,41 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const vm = require('node:vm'); +const { readdirSync, readFileSync } = require('node:fs'); +const { createHmac, createHash } = require('node:crypto'); +const core = require('../shared/core.cjs'); const hooks = require('../shared/hooks.cjs'); +const iml = { sha256: (text, encoding, key) => key ? createHmac('sha256', key).update(text).digest(encoding) : createHash('sha256').update(text).digest(encoding) }; +for (const file of readdirSync('make/functions')) { + const text = readFileSync('make/functions/' + file, 'utf8'); + assert.ok(text.length <= 5000); + const name = file.slice(0, -3); iml[name] = vm.runInNewContext(`(${text})`, { Buffer, URL, iml }); +} +const auth = { apiKey: 'synthetic-token', projectId: '11111111-1111-4111-8111-111111111111' }; +test('Make function output matches the shared public request across text, media and advanced calls', () => { + const p = { session: '739182640518203', recipientType: 'id', recipient: '739182640518204', text: 'Synthetic', idempotencyKey: 'synthetic-write-1', writeCreatedAt: new Date().toISOString() }; + for (const [action, extra] of [['sendText', {}], ['sendMedia', { mediaType: 'file', mediaSource: 'base64', media: 'AAECAw==' }], ['apiRequest', { method: 'GET', path: '/platform/sessions' }]]) { + const expected = core.buildRequest(action, { ...p, ...extra }, auth, 'make'); + const actual = iml.polymorfaRequest(action, { ...p, ...extra }, auth); + assert.deepEqual(JSON.parse(JSON.stringify(actual)), expected); + } +}); +test('Make uses the same authenticated subscription identity and refuses forged events', () => { + const expected = hooks.identity(auth, 'messageReceived', 'synthetic-key'); + const actual = iml.polymorfaHook(auth, 'messageReceived', 'synthetic-key'); + assert.deepEqual(JSON.parse(JSON.stringify(actual)), expected); + const event = require('../fixtures/events.json').messageReceived; + assert.equal(iml.polymorfaReceive(actual.name, actual.value, 'messageReceived', { [actual.name]: actual.value }, event), true); + assert.equal(iml.polymorfaReceive(actual.name, actual.value, 'messageReceived', {}, event), false); +}); +test('Make error function preserves completed/unknown write state without exposing response content', () => { + for (const code of ['idempotency_completed', 'idempotency_outcome_unknown']) assert.throws(() => iml.polymorfaResult(409, {}, { error: { code, message: auth.apiKey } }), e => e.code === code && !e.message.includes(auth.apiKey)); +}); +test('Make refuses an incomplete hook inventory instead of creating a duplicate', () => { + assert.throws(() => iml.polymorfaFindHook({ data: [], page: { hasMore: true } }, auth, 'https://example.com', 'messageReceived', 'key')); +}); +test('Make preserves an operator-disabled owned hook', () => { + const marker = hooks.identity(auth, 'messageReceived', 'key'); + const row = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://example.com', enabled: false, format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; + assert.throws(() => iml.polymorfaFindHook({ data: [row], page: { hasMore: false } }, auth, row.url, 'messageReceived', 'key')); +}); diff --git a/tests/native.test.cjs b/tests/native.test.cjs new file mode 100644 index 0000000..7736c62 --- /dev/null +++ b/tests/native.test.cjs @@ -0,0 +1,50 @@ +'use strict'; +const { test } = require('node:test'); const assert = require('node:assert/strict'); +const { Polymorfa } = require('../n8n/dist/nodes/Polymorfa/Polymorfa.node.js'); +const { PolymorfaTrigger } = require('../n8n/dist/nodes/Polymorfa/PolymorfaTrigger.node.js'); +const zapier = require('../zapier'); +const hooks = require('../shared/hooks.cjs'); +const auth = { apiKey: 'synthetic-project-token', projectId: '11111111-1111-4111-8111-111111111111' }; +const input = { operation: 'sendText', session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'Synthetic', idempotencyKey: 'synthetic-write-1', writeCreatedAt: new Date().toISOString() }; +test('compiled n8n node uses native request helpers, preserved input pairing and disabled redirects', async () => { + const calls = []; + const context = { + getCredentials: async () => auth, getInputData: () => [{ json: {} }], getNodeParameter: (key, i, fallback) => input[key] ?? fallback, getNode: () => ({ name: 'test', typeVersion: 1 }), + helpers: { httpRequest: async request => { calls.push(request); return { statusCode: 200, body: { success: true, data: { id: '9007199254740993124' } }, headers: {} }; } }, + }; + const result = await new Polymorfa().execute.call(context); + assert.equal(calls.length, 1); assert.equal(calls[0].disableFollowRedirect, true); assert.equal(calls[0].ignoreHttpStatusErrors, true); + assert.deepEqual(result[0][0].pairedItem, { item: 0 }); assert.equal(result[0][0].json.data.id, '9007199254740993124'); +}); +test('compiled n8n native binary input maps bytes through the existing base64 request contract', async () => { + let sent; + const values = { ...input, operation: 'sendMedia', mediaType: 'file', mediaSource: 'binary', binaryProperty: 'attachment' }; + const context = { getCredentials: async () => auth, getInputData: () => [{ json: {}, binary: { attachment: { mimeType: 'application/octet-stream' } } }], getNodeParameter: (k, i, f) => values[k] ?? f, getNode: () => ({ name: 'test' }), helpers: { getBinaryDataBuffer: async () => Buffer.from([0, 1, 2]), httpRequest: async r => { sent = r; return { statusCode: 200, body: { data: { id: '739182640518204' } } }; } } }; + await new Polymorfa().execute.call(context); + assert.deepEqual(sent.body.content.file, { base64: 'AAEC', mimeType: 'application/octet-stream' }); +}); +test('compiled n8n trigger rejects unauthenticated input and keeps event identity on valid input', async () => { + const marker = hooks.identity(auth, 'messageReceived', 'synthetic-trigger'); + const sample = require('../fixtures/events.json').messageReceived; + let incoming = {}; let status; + const context = { getCredentials: async () => auth, getNodeParameter: k => k === 'eventGroup' ? 'messageReceived' : 'synthetic-trigger', getHeaderData: () => incoming, getBodyData: () => sample, getResponseObject: () => ({ status(v) { status = v; return this; }, end() {} }), helpers: { returnJsonArray: value => [{ json: value }] } }; + const trigger = new PolymorfaTrigger(); + assert.deepEqual(await trigger.webhook.call(context), { noWebhookResponse: true }); assert.equal(status, 401); + incoming = { [marker.name]: marker.value }; + const valid = await trigger.webhook.call(context); assert.equal(valid.workflowData[0][0].json.id, sample.id); +}); +test('Zapier native action returns the real API envelope and never follows redirects', async () => { + let request; + const z = { request: async r => { request = r; return { status: 202, content: JSON.stringify({ success: true, data: { requestId: '11111111-1111-4111-8111-111111111111' } }), headers: {} }; } }; + const result = await zapier.creates.sendText.operation.perform(z, { authData: auth, inputData: input }); + assert.equal(result.data.requestId, '11111111-1111-4111-8111-111111111111'); assert.equal(request.redirect, 'manual'); +}); +test('Zapier native hook uses stable event IDs and synthetic setup samples without HMS reads', async () => { + const marker = hooks.identity(auth, 'messageReceived', 'synthetic-trigger'); const sample = require('../fixtures/events.json').messageReceived; + const operation = zapier.triggers.messageReceived.operation; + let requests = 0; const z = { request: () => { requests++; }, errors: { Error } }; + const bundle = { authData: auth, inputData: { subscriptionKey: 'synthetic-trigger' }, cleanedRequest: { headers: { [marker.name]: marker.value }, content: sample } }; + assert.deepEqual(await operation.perform(z, bundle), [sample]); + assert.equal((await operation.performList(z, bundle))[0].id, sample.id); assert.equal(requests, 0); + await assert.rejects(operation.perform(z, { ...bundle, cleanedRequest: { headers: {}, content: sample } })); +}); diff --git a/tests/pipedream.test.mjs b/tests/pipedream.test.mjs new file mode 100644 index 0000000..181af2a --- /dev/null +++ b/tests/pipedream.test.mjs @@ -0,0 +1,20 @@ +import { test } from 'node:test'; import assert from 'node:assert/strict'; +import action from '../pipedream/actions/sendText/sendText.mjs'; +import source from '../pipedream/sources/messageReceived/messageReceived.mjs'; +import hooks from '../shared/hooks.cjs'; +import { readFileSync } from 'node:fs'; +test('Pipedream action passes exact fields to the app request boundary', async () => { + const input = { session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'Synthetic', quotedMessageId: undefined, idempotencyKey: 'write-1', writeCreatedAt: '2026-11-01T12:00:00Z' }; + let call; + const result = await action.run.call({ ...input, polymorfa: { execute: async ($, key, values) => { call = { key, values }; return { data: { id: '739182640518204' } }; } } }, { $: {} }); + assert.deepEqual(call, { key: 'sendText', values: input }); assert.equal(result.data.id, '739182640518204'); +}); +test('Pipedream source rejects forgery and emits only the defined envelope with dedupe identity', async () => { + const auth = { apiKey: 'synthetic-token', projectId: '11111111-1111-4111-8111-111111111111' }; const key = 'source-1'; + const marker = hooks.identity(auth, 'messageReceived', key); const sample = JSON.parse(readFileSync('fixtures/events.json')).messageReceived; + const emitted = []; const responses = []; + const context = { subscriptionKey: key, polymorfa: { authenticate: (g, k, h) => hooks.authenticate(auth, g, k, h), envelope: hooks.envelope }, http: { respond: value => responses.push(value) }, $emit: (data, metadata) => emitted.push({ data, metadata }) }; + await source.run.call(context, { headers: {}, body: sample }); assert.equal(responses.at(-1).status, 401); assert.equal(emitted.length, 0); + await source.run.call(context, { headers: { [marker.name]: marker.value }, body: sample }); + assert.deepEqual(emitted[0].data, sample); assert.equal(emitted[0].metadata.id, sample.id); assert.equal(source.dedupe, 'unique'); +}); diff --git a/zapier/README.md b/zapier/README.md new file mode 100644 index 0000000..f957d32 --- /dev/null +++ b/zapier/README.md @@ -0,0 +1,34 @@ +# Polymorfa for Zapier + +The native integration source passes local schema validation. No Zapier app has +been registered, uploaded, promoted or listed by this repository. + +Use a restricted project token and its project UUID. Connection testing requires +`sessions:read`. Sends/reactions require `messages:write`, template lookup +`templates:read`, and REST Hook lifecycle `webhooks:read` plus `webhooks:manage`. + +Actions preserve the public Polymorfa response envelope. A 202 response is an +admission receipt, not a delivered-message receipt. Map opaque IDs as strings. +Keep the same business idempotency key and saved write-start timestamp on retry. +No automatic replacement write is made after completion or an unknown outcome. + +REST Hooks register their own project-wide endpoint and delete only that endpoint. +Use a unique subscription key for each Zap. The editor sample is labelled +synthetic and reads no hosted customer history. Test the real subscription, +samples, delivery, dedupe, cleanup, rotation and provider log redaction before +requesting public review. Provider acceptance of the synthetic sample path is +still required. + +Validate without contacting Zapier's style service: + +```sh +npm run validate:zapier +``` + +Public listing needs a Polymorfa-controlled publisher, a company-domain admin, +public setup docs, native test Zaps and an authorized support test account. +Record the approved Beta/Public status and actual directory URL. Do not submit a +duplicate app or promote a version merely because local validation passes. + +Zapier has its own execution, retention and billing. Its processing location is +separate from the Polymorfa region selected for a Number. diff --git a/zapier/index.js b/zapier/index.js new file mode 100644 index 0000000..e9e0fc0 --- /dev/null +++ b/zapier/index.js @@ -0,0 +1,61 @@ +'use strict'; +const { version: platformVersion } = require('zapier-platform-core'); +const { actions, fields, events } = require('./shared/catalog.cjs'); +const core = require('./shared/core.cjs'); +const hooks = require('./shared/hooks.cjs'); + +function transport(z) { + return async r => { + const response = await z.request({ method: r.method, url: r.url, headers: r.headers, params: r.query, body: r.body, redirect: 'manual', skipThrowForStatus: true }); + return { status: response.status, headers: response.headers, body: response.content }; + }; +} +function field(name) { + const f = fields[name]; + return { key: name, label: f.label, type: 'string', required: !!f.required, helpText: f.help, ...(f.options ? { choices: f.options } : {}), ...(f.default ? { default: f.default } : {}) }; +} +const creates = Object.fromEntries(Object.entries(actions).map(([key, action]) => [key, { + key, noun: 'Result', display: { label: action.label, description: action.description }, + operation: { + inputFields: action.fields.map(field), + async perform(z, bundle) { return core.execute(key, bundle.inputData, bundle.authData, transport(z), 'zapier'); }, + sample: { data: { id: '739182640518203' } }, + }, +}])); +const triggers = Object.fromEntries(Object.entries(events).map(([key, event]) => [key, { + key, noun: 'Event', display: { label: event.label, description: event.description }, + operation: { + type: 'hook', canPaginate: false, + inputFields: [{ key: 'subscriptionKey', label: 'Subscription Key', type: 'string', required: true, helpText: 'A unique stable key for this Zap trigger. Project-wide events are sent to Zapier. Use a different key for another trigger.' }], + async performSubscribe(z, bundle) { + // Zapier's logger scrubs authData values before its HTTP logs run. + // Register the derived receiver secret before the registration request. + bundle.authData.webhookVerificationSecret = hooks.identity(bundle.authData, key, bundle.inputData.subscriptionKey).value; + const owned = await hooks.subscribe(bundle.authData, bundle.targetUrl, key, bundle.inputData.subscriptionKey, transport(z), 'zapier'); + return { ...owned, url: bundle.targetUrl }; + }, + async performUnsubscribe(z, bundle) { + const owned = bundle.subscribeData; + await hooks.unsubscribe(bundle.authData, owned.id, owned.url, owned.group, owned.subscriptionKey, transport(z), 'zapier', owned.markerName); return {}; + }, + async perform(z, bundle) { + if (!hooks.authenticate(bundle.authData, key, bundle.inputData.subscriptionKey, bundle.cleanedRequest.headers)) throw new z.errors.Error('Webhook authentication failed.', 'WebhookAuthentication', 401); + return [hooks.envelope(bundle.cleanedRequest.content, key)]; + }, + async performList() { return [require('./shared/samples.json')[key]]; }, + sample: require('./shared/samples.json')[key], + outputFields: [{ key: 'id', type: 'string' }, { key: 'session', type: 'string' }, { key: 'timestamp', type: 'datetime' }, { key: 'event', type: 'string' }], + }, +}])); +module.exports = { + version: require('./package.json').version, platformVersion, + authentication: { + type: 'custom', connectionLabel: '{{projectId}}', + fields: [ + { key: 'apiKey', label: 'Project Token', type: 'password', required: true, helpText: 'A restricted project token. Connection testing requires sessions:read.' }, + { key: 'projectId', label: 'Project ID', type: 'string', required: true }, + ], + async test(z, bundle) { await core.execute('listNumbers', {}, bundle.authData, transport(z), 'zapier'); return { projectId: bundle.authData.projectId }; }, + }, + creates, triggers, +}; diff --git a/zapier/package.json b/zapier/package.json new file mode 100644 index 0000000..8c5e3c1 --- /dev/null +++ b/zapier/package.json @@ -0,0 +1,7 @@ +{ + "name": "polymorfa-zapier", "version": "0.1.0", "private": true, + "description": "First-party Polymorfa integration for Zapier", "license": "MIT", + "main": "index.js", "engines": { "node": "22.x" }, + "dependencies": { "zapier-platform-core": "19.3.0" }, + "scripts": { "validate": "zapier-platform validate --without-style" } +} diff --git a/zapier/shared/catalog.cjs b/zapier/shared/catalog.cjs new file mode 100644 index 0000000..e7ed5c9 --- /dev/null +++ b/zapier/shared/catalog.cjs @@ -0,0 +1,43 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const actions = { + sendText: { label: 'Send Text', description: 'Sends a text message or replies to a message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'text', 'quotedMessageId', 'idempotencyKey', 'writeCreatedAt'] }, + sendTemplate: { label: 'Send Template', description: 'Sends a template with its approved name and language.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'templateName', 'language', 'components', 'idempotencyKey', 'writeCreatedAt'] }, + sendMedia: { label: 'Send Media', description: 'Sends an image, video, file or voice message.', operation: 'sendMessage', fields: ['session', 'recipientType', 'recipient', 'mediaType', 'mediaSource', 'media', 'mimeType', 'caption', 'filename', 'idempotencyKey', 'writeCreatedAt'] }, + react: { label: 'React to Message', description: 'Adds or removes a reaction on a message.', operation: 'sendReaction', fields: ['session', 'recipientType', 'recipient', 'messageId', 'reaction', 'idempotencyKey', 'writeCreatedAt'] }, + listNumbers: { label: 'List Numbers', description: 'Lists Numbers visible to the connected project.', operation: 'listPlatformSessions', fields: [] }, + listTemplates: { label: 'List Templates', description: 'Lists message templates for a project.', operation: 'listProjectTemplates', fields: ['projectSlug'] }, + apiRequest: { label: 'Make an API Call', description: 'Performs an authorized call to a supported public Polymorfa operation.', operation: null, fields: ['method', 'path', 'requestBody', 'idempotencyKey', 'writeCreatedAt'] }, +}; +const fields = { + session: { label: 'Number', required: true, help: 'The exact Number identifier from Polymorfa. Keep it as a string.' }, + recipientType: { label: 'Recipient Type', required: true, options: { phoneNumber: 'Phone Number', id: 'Conversation ID', bsuid: 'Business-scoped User ID' }, default: 'phoneNumber' }, + recipient: { label: 'Recipient', required: true, help: 'Use an international phone number including +, a conversation ID, or a business-scoped user ID.' }, + text: { label: 'Text', required: true }, + quotedMessageId: { label: 'Reply to Message ID', help: 'Use the Polymorfa message ID, not a WhatsApp transport reference.' }, + templateName: { label: 'Template Name', required: true }, + language: { label: 'Template Language', required: true, help: 'The exact language code for the selected template.' }, + components: { label: 'Template Components', json: true, default: '[]', help: 'JSON array matching the template definition.' }, + mediaType: { label: 'Media Type', required: true, options: { image: 'Image', video: 'Video', file: 'File', voice: 'Voice' }, default: 'image' }, + mediaSource: { label: 'Media Source', required: true, options: { url: 'HTTPS URL', base64: 'Base64 Bytes' }, default: 'url' }, + media: { label: 'Media', required: true, help: 'HTTPS URL or raw base64 bytes. Base64 is limited to 4 MiB decoded by these adapters; provider/API limits can be lower.' }, + mimeType: { label: 'MIME Type' }, + caption: { label: 'Caption' }, + filename: { label: 'Filename', help: 'Used for file messages.' }, + messageId: { label: 'Message ID', required: true }, + reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, + writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, + projectSlug: { label: 'Project Slug', required: true }, + method: { label: 'Method', required: true, options: { GET: 'GET', POST: 'POST' }, default: 'GET' }, + path: { label: 'Path', required: true, help: 'A supported relative public API path, for example /platform/sessions. External hosts and credential operations are refused.' }, + requestBody: { label: 'Request Body', json: true, default: '{}', help: 'JSON object. Authentication and version headers are supplied by the connection.' }, +}; +const events = { + messageReceived: { label: 'New Message', description: 'Triggers when a message is received.', types: ['message.received'] }, + messageState: { label: 'Message State Changed', description: 'Triggers when a message is sent, acknowledged, failed or updated.', types: ['message.sent', 'message.ack', 'message.failed', 'message.update'] }, + numberState: { label: 'Number State Changed', description: 'Triggers when a Number changes state, connects or logs out.', types: ['session.status', 'session.connected', 'session.logged_out'] }, + callEnded: { label: 'Call Ended', description: 'Triggers when a call ends.', types: ['call.ended'] }, +}; +module.exports = { actions, fields, events }; diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs new file mode 100644 index 0000000..3df9eb8 --- /dev/null +++ b/zapier/shared/core.cjs @@ -0,0 +1,129 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const ORIGIN = 'https://api.polymorfa.com'; +const VERSION = '2026-09-22'; +const MAX_MEDIA_BYTES = 4 * 1024 * 1024; +const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +function required(value, name) { + if (typeof value !== 'string' || !value.trim()) throw new IntegrationError(`invalid_${name}`); + return value; +} +function segment(value, name) { + required(value, name); + if (!/^[A-Za-z0-9_-]{1,256}$/.test(value)) throw new IntegrationError(`invalid_${name}`); + return value; +} +function object(value, name) { + let parsed = value; + try { if (typeof value === 'string') parsed = JSON.parse(value); } catch { throw new IntegrationError(`invalid_${name}_json`); } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new IntegrationError(`invalid_${name}`); + return parsed; +} +function conversation(p) { + if (!['phoneNumber', 'id', 'bsuid'].includes(p.recipientType)) throw new IntegrationError('invalid_recipient_type'); + required(p.recipient, 'recipient'); + if (p.recipientType === 'phoneNumber' && !/^\+[1-9]\d{1,14}$/.test(p.recipient)) throw new IntegrationError('invalid_phone_number'); + if (p.recipientType === 'id' && !/^[1-9]\d{0,18}$/.test(p.recipient)) throw new IntegrationError('invalid_conversation_id'); + if (p.recipientType === 'bsuid' && !/^[A-Z]{2}\.[A-Za-z0-9]{1,128}$/.test(p.recipient)) throw new IntegrationError('invalid_bsuid'); + return { [p.recipientType]: p.recipient }; +} +function messageBody(action, p) { + const body = { conversation: conversation(p) }; + if (action === 'sendText') { + body.content = { text: required(p.text, 'text') }; + if (p.quotedMessageId) { + if (!/^[1-9]\d{0,18}$/.test(required(p.quotedMessageId, 'quoted_message_id'))) throw new IntegrationError('invalid_quoted_message_id'); + body.quotedMessage = { id: p.quotedMessageId }; + } + } else if (action === 'sendTemplate') { + let components; + try { components = typeof p.components === 'string' ? JSON.parse(p.components || '[]') : (p.components ?? []); } catch { throw new IntegrationError('invalid_components_json'); } + if (!Array.isArray(components) || components.length > 1000) throw new IntegrationError('invalid_components'); + body.content = { template: { name: required(p.templateName, 'template_name'), language: required(p.language, 'language'), components } }; + if (p.language.length < 2) throw new IntegrationError('invalid_language'); + } else if (action === 'sendMedia') { + if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); + required(p.media, 'media'); + if (p.mediaSource === 'url') { + let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } + if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + throw new IntegrationError('invalid_or_oversized_media'); + } + const media = { [p.mediaSource]: p.media }; + for (const key of ['mimeType', 'caption']) if (p[key]) media[key] = required(p[key], key); + if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); + body.content = { [p.mediaType]: media }; + } else if (action === 'react') { + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = p.reaction; + } else throw new IntegrationError('unknown_action'); + return body; +} +function writeHeaders(p, now = Date.now()) { + const key = required(p.idempotencyKey, 'idempotency_key'); + if (Buffer.byteLength(key, 'utf8') > 255 || /[\x00-\x1f\x7f]/.test(key)) throw new IntegrationError('invalid_idempotency_key'); + const created = Date.parse(required(p.writeCreatedAt, 'write_started_at')); + if (!Number.isFinite(created) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(p.writeCreatedAt) || created > now + 60000 || now - created >= MAX_WRITE_AGE) throw new IntegrationError('write_requires_reconciliation'); + return { 'Idempotency-Key': key }; +} +function headers(auth, provider) { + const token = required(auth.apiKey, 'project_token'); + if (/[\r\n]/.test(token)) throw new IntegrationError('invalid_project_token'); + segment(auth.projectId, 'project_id'); + return { Authorization: `Bearer ${token}`, 'Polymorfa-Version': VERSION, 'Content-Type': 'application/json', 'User-Agent': `polymorfa-${provider}/0.1.0` }; +} +function advanced(p, auth) { + const path = required(p.path, 'path'); + if (!path.startsWith('/') || path.startsWith('//') || /[%\\?#\s]/.test(path) || path.split('/').some(x => x === '.' || x === '..')) throw new IntegrationError('unsupported_path'); + const method = p.method; + const allowed = (method === 'GET' && (path === '/platform/sessions' || /^\/messaging\/projects\/[A-Za-z0-9_-]+\/templates(?:\/[A-Za-z0-9_-]+)?$/.test(path))) || + (method === 'POST' && /^\/messaging\/[A-Za-z0-9_-]+\/messages\/(send|react)$/.test(path)); + if (!allowed) throw new IntegrationError('unsupported_operation'); + const body = method === 'POST' ? object(p.requestBody, 'request_body') : undefined; + if (body) { + const keys = path.endsWith('/react') ? ['conversation', 'id', 'reaction'] : ['conversation', 'isForwarded', 'mentions', 'quotedMessage', 'content']; + if (Object.keys(body).some(k => !keys.includes(k))) throw new IntegrationError('unsupported_request_field'); + } + return { method, url: ORIGIN + path, ...(body ? { body } : {}), ...(path === '/platform/sessions' ? { query: { projectId: auth.projectId } } : {}) }; +} +function buildRequest(action, p, auth, provider = 'integration', now) { + let r; + if (['sendText', 'sendTemplate', 'sendMedia', 'react'].includes(action)) r = { method: 'POST', url: `${ORIGIN}/messaging/${segment(p.session, 'number')}/messages/${action === 'react' ? 'react' : 'send'}`, body: messageBody(action, p) }; + else if (action === 'listNumbers') r = { method: 'GET', url: ORIGIN + '/platform/sessions', query: { projectId: auth.projectId } }; + else if (action === 'listTemplates') r = { method: 'GET', url: `${ORIGIN}/messaging/projects/${segment(p.projectSlug, 'project_slug')}/templates` }; + else if (action === 'apiRequest') r = advanced(p, auth); + else throw new IntegrationError('unknown_action'); + r.headers = { ...headers(auth, provider), ...(r.method === 'POST' ? writeHeaders(p, now) : {}) }; + return r; +} +function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(v) ? v : fallback; } +function parseResponse(response) { + const status = response.status; + let body = response.body; + try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + if (status < 200 || status >= 300) { + const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); + const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); + const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + } + if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); + return body; +} +async function execute(action, p, auth, transport, provider, now) { + const request = buildRequest(action, p, auth, provider, now); + let response; + try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } + return parseResponse(response); +} +module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/zapier/shared/hooks.cjs b/zapier/shared/hooks.cjs new file mode 100644 index 0000000..0ddb3f9 --- /dev/null +++ b/zapier/shared/hooks.cjs @@ -0,0 +1,92 @@ +// Generated from shared/. Run npm run generate. +'use strict'; + +const { createHmac, timingSafeEqual } = require('node:crypto'); +const { events } = require('./catalog.cjs'); +const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); +function identity(auth, group, subscriptionKey) { + if (!events[group]) throw new IntegrationError('invalid_event_group'); + required(subscriptionKey, 'subscription_key'); + if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); + const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); + const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; +} +function callback(url) { + let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } + if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); + return url; +} +function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { + const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); + const types = events[group].types; + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && + hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); +} +async function call(auth, transport, provider, method, path, body, key, query) { + let response; + try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } + catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } + return parseResponse(response); +} +async function find(auth, url, group, subscriptionKey, transport, provider) { + let cursor; const seen = new Set(); const matches = []; + for (let i = 0; i < 100; i++) { + const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); + if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); + const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); + if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + for (const hook of r.data) { + const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); + if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); + } + if (!r.page?.hasMore) { + if (matches.length > 1) throw new IntegrationError('duplicate_owned_subscriptions'); + return matches[0]; + } + if (typeof r.page.nextCursor !== 'string' || seen.has(r.page.nextCursor)) throw new IntegrationError('invalid_subscription_pagination'); + cursor = r.page.nextCursor; seen.add(cursor); + } + throw new IntegrationError('subscription_inventory_requires_reconciliation'); +} +async function subscribe(auth, url, group, subscriptionKey, transport, provider) { + callback(url); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const existing = await find(auth, url, group, subscriptionKey, transport, provider); + if (existing) return record(existing); + const marker = identity(auth, group, subscriptionKey); + // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + try { + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + return record(r.data.webhook); + } catch { + // A lost response may follow a successful create. Reconcile; never blindly create again. + const recovered = await find(auth, url, group, subscriptionKey, transport, provider); + if (recovered) return record(recovered); + throw new IntegrationError('subscription_requires_reconciliation'); + } +} +async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { + const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; + let hook; + try { hook = (await call(auth, transport, provider, 'GET', path)).data; } + catch (error) { if (error.status === 404) return; throw error; } + // Disabled hooks remain owned and may be deleted; all other ownership checks still apply. + if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); + await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); +} +function authenticate(auth, group, subscriptionKey, incomingHeaders) { + const expected = identity(auth, group, subscriptionKey); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; + return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); +} +function envelope(body, group) { + if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); + // Copy the defined envelope only. Never export arbitrary top-level transport fields. + return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; +} +module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/zapier/shared/samples.json b/zapier/shared/samples.json new file mode 100644 index 0000000..a5b25e2 --- /dev/null +++ b/zapier/shared/samples.json @@ -0,0 +1,65 @@ +{ + "messageReceived": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a71", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.received", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "fromMe": false, + "timestamp": 1793534400, + "pushName": "Synthetic contact", + "isGroup": false, + "type": "text", + "text": "Synthetic setup sample. No customer message was read." + } + }, + "messageState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a72", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "message.sent", + "payload": { + "id": "739182640518204", + "whatsapp_ids": { + "linked_devices": "SYNTHETIC-EXAMPLE" + }, + "conversation": { + "id": "739182640518205" + }, + "type": "text", + "timestamp": 1793534400 + } + }, + "numberState": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a73", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "session.status", + "payload": { + "status": "connected" + } + }, + "callEnded": { + "id": "0190f2a4-6c1e-7b9d-9f3a-2e8c4d5b6a74", + "session": "739182640518203", + "timestamp": "2026-11-01T12:00:00.000Z", + "event": "call.ended", + "payload": { + "from": { + "id": "739182640518205" + }, + "callId": "SYNTHETIC-CALL", + "durationSeconds": 60, + "reason": "ended", + "direction": "inbound", + "hadVideo": false + } + } +} From d18d9bd7713a31fb38adeeba1bcf3b9a8d28a0af Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 1 Nov 2026 19:53:01 +0200 Subject: [PATCH 02/14] Use explicit icon colors for n8n themes --- n8n/nodes/Polymorfa/polymorfa.dark.svg | 4 ++-- n8n/nodes/Polymorfa/polymorfa.light.svg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/n8n/nodes/Polymorfa/polymorfa.dark.svg b/n8n/nodes/Polymorfa/polymorfa.dark.svg index b951ebf..bef3e06 100644 --- a/n8n/nodes/Polymorfa/polymorfa.dark.svg +++ b/n8n/nodes/Polymorfa/polymorfa.dark.svg @@ -1,3 +1,3 @@ - - + + diff --git a/n8n/nodes/Polymorfa/polymorfa.light.svg b/n8n/nodes/Polymorfa/polymorfa.light.svg index b951ebf..3bb1d18 100644 --- a/n8n/nodes/Polymorfa/polymorfa.light.svg +++ b/n8n/nodes/Polymorfa/polymorfa.light.svg @@ -1,3 +1,3 @@ - - + + From 4c660b816dbb13b79b69adb4db8b4d55c01a45bc Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 1 Nov 2026 20:03:09 +0200 Subject: [PATCH 03/14] Keep Make media validation within documented JavaScript globals --- make/functions/polymorfaBody.js | 6 ++++-- n8n/shared/core.js | 6 ++++-- pipedream/common/core.cjs | 6 ++++-- shared/core.cjs | 6 ++++-- tests/make.test.cjs | 4 ++-- zapier/shared/core.cjs | 6 ++++-- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/make/functions/polymorfaBody.js b/make/functions/polymorfaBody.js index a544240..14d15d8 100644 --- a/make/functions/polymorfaBody.js +++ b/make/functions/polymorfaBody.js @@ -38,8 +38,10 @@ function messageBody(action, p) { if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); required(p.media, 'media'); if (p.mediaSource === 'url') { - let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } - if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + // Use only ECMAScript/Buffer globals: Make does not promise the URL Web API. + // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. + const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); + if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } diff --git a/n8n/shared/core.js b/n8n/shared/core.js index 3df9eb8..7b0b97f 100644 --- a/n8n/shared/core.js +++ b/n8n/shared/core.js @@ -54,8 +54,10 @@ function messageBody(action, p) { if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); required(p.media, 'media'); if (p.mediaSource === 'url') { - let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } - if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + // Use only ECMAScript/Buffer globals: Make does not promise the URL Web API. + // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. + const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); + if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs index 3df9eb8..7b0b97f 100644 --- a/pipedream/common/core.cjs +++ b/pipedream/common/core.cjs @@ -54,8 +54,10 @@ function messageBody(action, p) { if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); required(p.media, 'media'); if (p.mediaSource === 'url') { - let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } - if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + // Use only ECMAScript/Buffer globals: Make does not promise the URL Web API. + // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. + const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); + if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } diff --git a/shared/core.cjs b/shared/core.cjs index 44d9f85..95371a5 100644 --- a/shared/core.cjs +++ b/shared/core.cjs @@ -53,8 +53,10 @@ function messageBody(action, p) { if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); required(p.media, 'media'); if (p.mediaSource === 'url') { - let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } - if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + // Use only ECMAScript/Buffer globals: Make does not promise the URL Web API. + // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. + const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); + if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } diff --git a/tests/make.test.cjs b/tests/make.test.cjs index 37a00f2..0d5e252 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -9,12 +9,12 @@ const iml = { sha256: (text, encoding, key) => key ? createHmac('sha256', key).u for (const file of readdirSync('make/functions')) { const text = readFileSync('make/functions/' + file, 'utf8'); assert.ok(text.length <= 5000); - const name = file.slice(0, -3); iml[name] = vm.runInNewContext(`(${text})`, { Buffer, URL, iml }); + const name = file.slice(0, -3); iml[name] = vm.runInNewContext(`(${text})`, { Buffer, iml }); } const auth = { apiKey: 'synthetic-token', projectId: '11111111-1111-4111-8111-111111111111' }; test('Make function output matches the shared public request across text, media and advanced calls', () => { const p = { session: '739182640518203', recipientType: 'id', recipient: '739182640518204', text: 'Synthetic', idempotencyKey: 'synthetic-write-1', writeCreatedAt: new Date().toISOString() }; - for (const [action, extra] of [['sendText', {}], ['sendMedia', { mediaType: 'file', mediaSource: 'base64', media: 'AAECAw==' }], ['apiRequest', { method: 'GET', path: '/platform/sessions' }]]) { + for (const [action, extra] of [['sendText', {}], ['sendMedia', { mediaType: 'file', mediaSource: 'base64', media: 'AAECAw==' }], ['sendMedia', { mediaType: 'image', mediaSource: 'url', media: 'https://example.com/synthetic.png' }], ['apiRequest', { method: 'GET', path: '/platform/sessions' }]]) { const expected = core.buildRequest(action, { ...p, ...extra }, auth, 'make'); const actual = iml.polymorfaRequest(action, { ...p, ...extra }, auth); assert.deepEqual(JSON.parse(JSON.stringify(actual)), expected); diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs index 3df9eb8..7b0b97f 100644 --- a/zapier/shared/core.cjs +++ b/zapier/shared/core.cjs @@ -54,8 +54,10 @@ function messageBody(action, p) { if (!['image', 'video', 'file', 'voice'].includes(p.mediaType) || !['url', 'base64'].includes(p.mediaSource)) throw new IntegrationError('invalid_media_type'); required(p.media, 'media'); if (p.mediaSource === 'url') { - let u; try { u = new URL(p.media); } catch { throw new IntegrationError('invalid_media_url'); } - if (u.protocol !== 'https:' || u.username || u.password) throw new IntegrationError('invalid_media_url'); + // Use only ECMAScript/Buffer globals: Make does not promise the URL Web API. + // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. + const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); + if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } From 19c368398b6e338daf7b1a1588ee38898d473734 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Mon, 2 Nov 2026 03:54:53 +0200 Subject: [PATCH 04/14] Add scoped Make authoring access check --- make/README.md | 36 ++++++++++++++++++++++++++++ package-lock.json | 11 +++++++++ package.json | 4 +++- scripts/make-access.mjs | 48 ++++++++++++++++++++++++++++++++++++++ tests/make-access.test.mjs | 46 ++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 scripts/make-access.mjs create mode 100644 tests/make-access.test.mjs diff --git a/make/README.md b/make/README.md index 58cdcc4..201c1f6 100644 --- a/make/README.md +++ b/make/README.md @@ -5,6 +5,42 @@ app metadata; `base.json` holds shared request settings. Component directories separate metadata, parameters, communication and interfaces for review/upload through Make's app tooling. They are source artifacts, not a scenario blueprint. +Marketplace connections are being revised to use browser-based OAuth consent. +The API-key connection files remain an earlier source candidate. They are not +the accepted marketplace login experience and have not been uploaded. + +## Authoring access + +The management client is pinned to `@makehq/sdk` 1.9.0 in the root lockfile. It +requires Node 20 or later; this repository uses its pinned Node 24 builder. +Custom-app authoring uses `make.sdk`, separate from runtime connections and the +Polymorfa OAuth client that customers will authorize. + +Sign in to the intended Make account yourself and copy the organization's zone +hostname from its dashboard URL. Do not infer it from location. In Profile > +API, create a token with only `sdk-apps:read` and `sdk-apps:write`. Store the raw +token as `MAKE_API_KEY` and the hostname as `MAKE_ZONE` in the runner's secure +environment. An ignored local `.env` is also supported through Node's loader: + +```sh +node --env-file=.env scripts/make-access.mjs +``` + +With the variables already loaded, run `npm run make:access`. The command uses +`new Make(apiKey, zone)` and `make.sdk.apps.list({ cols: ['name', 'version'] })`. +An empty list is valid. This checks read access only, never writes, creates a +runtime connection, or runs a scenario. Verify the write scope in the token's +settings. No team ID or extra scopes are required for this check. + +Resolve the exact app/version and origin zone before editing an existing app. +Private upload and complete component readback remain pending the OAuth +contract, authoring access and native schema corrections. No upload command is +implemented by the access check. + +Token setup: [official guide](https://developers.make.com/api-documentation/authentication/create-authentication-token). + +## Component candidate + `functions/` contains generated custom IML functions, each below 5,000 characters. Make must enable custom functions for the publisher. Local tests execute their JavaScript with a small IML shim; they do not qualify Make's actual interpreter, diff --git a/package-lock.json b/package-lock.json index 86a75e2..e96f549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "zapier" ], "devDependencies": { + "@makehq/sdk": "1.9.0", "@n8n/node-cli": "0.54.6", "@pipedream/platform": "3.4.0", "@types/node": "22.19.1", @@ -3033,6 +3034,16 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@makehq/sdk": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@makehq/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-VzZOZy5XVYNr5Iz4xvL5i7KcNNWHSqaNgTyT1jNA58xudt2YIUdWmi6GlDssnD3jYygmkUuPRsJrwbw1Px8aoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", diff --git a/package.json b/package.json index a71bafc..26a4323 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "build": "npm run generate && npm run build --workspace n8n-nodes-polymorfa", "test": "node --test --test-reporter=spec tests/*.test.cjs tests/*.test.mjs", "check": "npm run check:contracts && npm run check:generated && npm run build && npm test", - "validate:zapier": "npm run validate --workspace polymorfa-zapier" + "validate:zapier": "npm run validate --workspace polymorfa-zapier", + "make:access": "node scripts/make-access.mjs" }, "devDependencies": { + "@makehq/sdk": "1.9.0", "@n8n/node-cli": "0.54.6", "@pipedream/platform": "3.4.0", "@types/node": "22.19.1", diff --git a/scripts/make-access.mjs b/scripts/make-access.mjs new file mode 100644 index 0000000..e3acd0d --- /dev/null +++ b/scripts/make-access.mjs @@ -0,0 +1,48 @@ +// @ts-check +import { Make } from '@makehq/sdk'; +import { pathToFileURL } from 'node:url'; + +/** Check app-definition read access only. Never create an app to test a token. + * @param {NodeJS.ProcessEnv} env + */ +export async function checkMakeAccess(env = process.env) { + const apiKey = env.MAKE_API_KEY; + const zone = env.MAKE_ZONE; + if (!apiKey || !zone) { + throw new Error('Set MAKE_API_KEY and MAKE_ZONE securely in the runner environment.'); + } + if (apiKey.trim() !== apiKey || !/^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i.test(zone)) { + throw new Error('Use the raw token and the exact Make zone hostname without a scheme, path or whitespace.'); + } + const make = new Make(apiKey, zone); + try { + const apps = await make.sdk.apps.list({ cols: ['name', 'version'] }); + // Project only the requested non-secret identity fields, even if a server + // returns extra fields. Do not log the client, headers or error body. + return { + zone, + readAccess: true, + writeAccess: 'not tested; verify sdk-apps:write in the token settings', + apps: apps.map(({ name, version }) => ({ name, version })), + }; + } catch (error) { + const status = error && typeof error === 'object' && 'statusCode' in error ? error.statusCode : undefined; + if (status === 401) throw new Error('Make returned 401. Check the token belongs to the selected zone and remains valid.'); + if (status === 403) throw new Error('Make returned 403. Check sdk-apps:read and the account permissions in the selected zone. App ownership must be checked separately before editing.'); + throw new Error('Make app listing failed. No app was created or changed; the response body was omitted.'); + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (process.argv.length > 2) { + console.error('This command takes no arguments. Load MAKE_API_KEY and MAKE_ZONE through the secure environment.'); + process.exitCode = 1; + } else { + try { + console.log(JSON.stringify(await checkMakeAccess(), null, 2)); + } catch (error) { + console.error(error instanceof Error ? error.message : 'Make access check failed.'); + process.exitCode = 1; + } + } +} diff --git a/tests/make-access.test.mjs b/tests/make-access.test.mjs new file mode 100644 index 0000000..856c033 --- /dev/null +++ b/tests/make-access.test.mjs @@ -0,0 +1,46 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { checkMakeAccess } from '../scripts/make-access.mjs'; + +const env = { MAKE_API_KEY: 'synthetic-not-a-token', MAKE_ZONE: 'eu1.make.com' }; + +test('Make authoring access refuses missing credentials or malformed zones before any request', async t => { + const fetch = t.mock.method(globalThis, 'fetch', () => { throw new Error('Unexpected network request'); }); + for (const invalid of [{}, { MAKE_ZONE: env.MAKE_ZONE }, { ...env, MAKE_ZONE: 'https://eu1.make.com/api/v2' }, { ...env, MAKE_API_KEY: ' token ' }]) { + await assert.rejects(checkMakeAccess(invalid)); + } + assert.equal(fetch.mock.callCount(), 0); +}); + +test('Make authoring access uses the installed SDK, accepts an empty app list and performs only GET', async t => { + const fetch = t.mock.method(globalThis, 'fetch', async (input, init) => { + const url = new URL(String(input)); + assert.equal(url.origin, 'https://eu1.make.com'); + assert.equal(url.pathname, '/api/v2/sdk/apps'); + assert.equal(init?.method ?? 'GET', 'GET'); + assert.match(decodeURIComponent(url.search), /name/); + assert.match(decodeURIComponent(url.search), /version/); + return Response.json({ apps: [] }); + }); + const result = await checkMakeAccess(env); + assert.equal(result.readAccess, true); + assert.deepEqual(result.apps, []); + assert.match(result.writeAccess, /not tested/); + assert.equal(fetch.mock.callCount(), 1); +}); + +test('Make authoring access omits extra app fields and credential-bearing error bodies', async t => { + t.mock.method(globalThis, 'fetch', async () => Response.json({ apps: [{ name: 'polymorfa-test', version: 1, secret: env.MAKE_API_KEY }] })); + assert.deepEqual((await checkMakeAccess(env)).apps, [{ name: 'polymorfa-test', version: 1 }]); + t.mock.restoreAll(); + for (const status of [401, 403, 500]) { + const fetch = t.mock.method(globalThis, 'fetch', async () => Response.json({ message: env.MAKE_API_KEY }, { status })); + await assert.rejects(checkMakeAccess(env), error => { + assert.ok(!error.message.includes(env.MAKE_API_KEY)); + assert.match(error.message, status === 500 ? /listing failed/ : new RegExp(String(status))); + return true; + }); + assert.equal(fetch.mock.callCount(), 1); + t.mock.restoreAll(); + } +}); From 9dae5e35dffcf173d311d79c58b7f92cdd9eb072 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 04:51:35 +0200 Subject: [PATCH 05/14] fix: address native integration and publishing review findings --- .github/workflows/publish-n8n.yml | 10 +++ CHANGELOG.md | 8 +++ MAINTAINING.md | 12 ++++ make/base.json | 15 ++++- make/functions/polymorfaCallback.js | 7 +++ make/functions/polymorfaCreateKey.js | 5 ++ make/functions/polymorfaDetachHook.js | 8 +++ make/functions/polymorfaError.js | 4 ++ make/functions/polymorfaFindHook.js | 7 ++- make/functions/polymorfaHook.js | 4 +- make/functions/polymorfaOwnedHook.js | 2 +- make/functions/polymorfaResult.js | 5 +- make/modules/apiRequest/metadata.json | 2 +- make/webhooks/callEnded/attach.json | 5 +- make/webhooks/callEnded/detach.json | 12 ++-- make/webhooks/messageReceived/attach.json | 5 +- make/webhooks/messageReceived/detach.json | 12 ++-- make/webhooks/messageState/attach.json | 5 +- make/webhooks/messageState/detach.json | 12 ++-- make/webhooks/numberState/attach.json | 5 +- make/webhooks/numberState/detach.json | 12 ++-- n8n/README.md | 5 ++ n8n/nodes/Polymorfa/Polymorfa.node.ts | 4 ++ n8n/shared/core.js | 5 +- n8n/shared/hooks.js | 27 +++++---- package.json | 3 +- pipedream/common/core.cjs | 5 +- pipedream/common/hooks.cjs | 27 +++++---- recipes/n8n/order-notification.json | 74 +++++++++++++++++++++-- scripts/check-upstream.mjs | 2 +- scripts/generate.mjs | 23 ++++--- scripts/release-gate.mjs | 5 ++ scripts/release-policy.mjs | 9 +++ shared/core.cjs | 5 +- shared/hooks.cjs | 27 +++++---- tests/core.test.cjs | 5 ++ tests/hooks.test.cjs | 36 +++++++++++ tests/make.test.cjs | 30 +++++++++ tests/native.test.cjs | 24 ++++++++ tests/recipes.test.cjs | 14 +++++ tests/release-policy.test.mjs | 11 ++++ tests/sdk-drift.test.mjs | 45 ++++++++++++++ zapier/index.js | 5 +- zapier/shared/core.cjs | 5 +- zapier/shared/hooks.cjs | 27 +++++---- 45 files changed, 477 insertions(+), 103 deletions(-) create mode 100644 make/functions/polymorfaCallback.js create mode 100644 make/functions/polymorfaCreateKey.js create mode 100644 make/functions/polymorfaDetachHook.js create mode 100644 make/functions/polymorfaError.js create mode 100644 scripts/release-policy.mjs create mode 100644 tests/recipes.test.cjs create mode 100644 tests/release-policy.test.mjs create mode 100644 tests/sdk-drift.test.mjs diff --git a/.github/workflows/publish-n8n.yml b/.github/workflows/publish-n8n.yml index 9c4a630..8211457 100644 --- a/.github/workflows/publish-n8n.yml +++ b/.github/workflows/publish-n8n.yml @@ -6,10 +6,15 @@ on: description: Reviewed package version required: true type: string + reviewed-source: + description: Immutable reviewed source commit from the approved release receipt + required: true + type: string permissions: contents: read jobs: publish: + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: npm-release permissions: @@ -19,6 +24,8 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + ref: ${{ github.sha }} + persist-credentials: false - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version-file: .nvmrc @@ -31,4 +38,7 @@ jobs: - run: node scripts/release-gate.mjs env: RELEASE_VERSION: ${{ inputs.version }} + RELEASE_REVIEWED_SOURCE: ${{ inputs.reviewed-source }} + RELEASE_REF: ${{ github.ref }} + RELEASE_SOURCE: ${{ github.sha }} - run: npm publish --workspace n8n-nodes-polymorfa --provenance --access public diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb61bd..7d68525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +- Separate webhook receiver secrets from public marker/idempotency metadata; + use a fresh create identity after a deleted subscription. +- Preserve definite subscription errors and non-JSON uncertain outcomes; + normalize callback ownership and native Zapier response headers. +- Keep successful n8n batch items when Continue On Fail is enabled. Require + saved notification identities before recipe sends and write identities in Zapier. +- Restrict n8n publication dispatch to main and bind its reviewed source receipt. + - First-party source for n8n, Make, Zapier and Pipedream with a common public API and SDK contract pin. - Text/replies, templates, URL/base64 media, reactions, Number/template lookup diff --git a/MAINTAINING.md b/MAINTAINING.md index 942e4ba..bbcaa13 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -48,6 +48,18 @@ npm trusted publisher before use. No provider account, environment protection or trusted publisher has been configured by adding this workflow. Review is still required; the gate is bookkeeping and does not authenticate human authorization. +Publication accepts only a `main` dispatch, checks out its immutable commit, and +requires the dispatch's reviewed-source commit to match the release receipt and +its executable files. Restrict the `npm-release` environment to protected `main` +and require an independent release reviewer before enabling the trusted publisher. +These external settings cannot be secured by editable workflow code alone. + +Webhook marker names, receiver verifiers and create-key seeds now use separate +HMAC domains. Each new activation adds its own nonce to the create key so a +deleted hook cannot be recovered from an old create receipt. This is a change +before the first release; remove old development subscriptions with their recorded +ownership metadata before recreating them. No production migration is implied. + Keep API credentials in each provider's credential store. Webhook receivers use an authenticated per-subscription header supported by the public subscription contract. A stable unique subscription key determines its private verifier. diff --git a/make/base.json b/make/base.json index 23bccb6..98b0c7c 100644 --- a/make/base.json +++ b/make/base.json @@ -8,13 +8,24 @@ "log": { "sanitize": [ "request.headers.authorization", + "request.headers.Authorization", + "request.headers.idempotency-key", + "request.headers.Idempotency-Key", "request.body.headers", - "response.body.data.secret" + "response.body.data.secret", + "response.body.data.idempotency.key" ] }, "response": { "error": { - "message": "[{{statusCode}}] Polymorfa {{body.error.code}}" + "409": { + "message": "{{polymorfaError(statusCode; headers; body)}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body)}}" + }, + "message": "{{polymorfaError(statusCode; headers; body)}}" } } } diff --git a/make/functions/polymorfaCallback.js b/make/functions/polymorfaCallback.js new file mode 100644 index 0000000..7fe514c --- /dev/null +++ b/make/functions/polymorfaCallback.js @@ -0,0 +1,7 @@ +function polymorfaCallback(value) { + +if (typeof value !== 'string' || /[\s\\\x00-\x1f\x7f]/.test(value)) throw new Error('Invalid callback.'); +const parts = value.match(/^https:\/\/([^/?#]+)([^?#]*)(\?[^#]*)?$/i); +if (!parts || parts[1].includes('@')) throw new Error('Invalid callback.'); +return 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || ''); +} diff --git a/make/functions/polymorfaCreateKey.js b/make/functions/polymorfaCreateKey.js new file mode 100644 index 0000000..30dd374 --- /dev/null +++ b/make/functions/polymorfaCreateKey.js @@ -0,0 +1,5 @@ +function polymorfaCreateKey(key, nonce) { + +if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9-]{16,64}$/.test(nonce)) throw new Error('Missing webhook activation identity.'); +return 'integration-create-' + key + '-' + nonce; +} diff --git a/make/functions/polymorfaDetachHook.js b/make/functions/polymorfaDetachHook.js new file mode 100644 index 0000000..c3b738a --- /dev/null +++ b/make/functions/polymorfaDetachHook.js @@ -0,0 +1,8 @@ +function polymorfaDetachHook(body, id, projectId, ownedUrl, markerName, group) { + +if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.'); +const matches = body.data.filter(h => h.id === id); +if (matches.length > 1) throw new Error('Duplicate subscription IDs need reconciliation.'); +if (!matches.length) return false; +return iml.polymorfaOwnedHook({ data: matches[0] }, projectId, ownedUrl, markerName, group); +} diff --git a/make/functions/polymorfaError.js b/make/functions/polymorfaError.js new file mode 100644 index 0000000..7649952 --- /dev/null +++ b/make/functions/polymorfaError.js @@ -0,0 +1,4 @@ +function polymorfaError(status, headers, body) { + +try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; } +} diff --git a/make/functions/polymorfaFindHook.js b/make/functions/polymorfaFindHook.js index 70db7fa..f1d15a2 100644 --- a/make/functions/polymorfaFindHook.js +++ b/make/functions/polymorfaFindHook.js @@ -1,10 +1,11 @@ function polymorfaFindHook(body, auth, url, group, key) { const expected = iml.polymorfaHook(auth, group, key); const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; +const same = h => iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url); if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.'); -if (body.data.some(h => h.url !== url && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.'); -const owns = h => h.projectId === auth.projectId && h.owner === 'project' && h.url === url && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t)); -if (body.data.some(h => (h.url === url || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.'); +if (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.'); +const owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t)); +if (body.data.some(h => (same(h) || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.'); const matches = body.data.filter(owns); if (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.'); return matches[0]?.id || null; diff --git a/make/functions/polymorfaHook.js b/make/functions/polymorfaHook.js index dd91fda..9cc23c2 100644 --- a/make/functions/polymorfaHook.js +++ b/make/functions/polymorfaHook.js @@ -21,6 +21,6 @@ if (!events[group]) throw new IntegrationError('invalid_event_group'); required(subscriptionKey, 'subscription_key'); if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); -const secret = iml.sha256(scope, 'hex', auth.apiKey); -return { name: 'X-Polymorfa-Integration-Token-' + secret.slice(0, 24), value: secret, key: secret }; +const derive = label => iml.sha256(label + ':' + scope, 'hex', auth.apiKey); +return { name: 'X-Polymorfa-Integration-Token-' + derive('marker').slice(0, 24), value: derive('receiver'), key: derive('create') }; } diff --git a/make/functions/polymorfaOwnedHook.js b/make/functions/polymorfaOwnedHook.js index 379bee9..39afa53 100644 --- a/make/functions/polymorfaOwnedHook.js +++ b/make/functions/polymorfaOwnedHook.js @@ -1,6 +1,6 @@ function polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group) { const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; const h = body.data; -if (!h || h.owner !== 'project' || h.projectId !== projectId || h.url !== ownedUrl || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.'); +if (!h || h.owner !== 'project' || h.projectId !== projectId || iml.polymorfaCallback(h.url) !== iml.polymorfaCallback(ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.'); return true; } diff --git a/make/functions/polymorfaResult.js b/make/functions/polymorfaResult.js index 491979f..5a8c1fe 100644 --- a/make/functions/polymorfaResult.js +++ b/make/functions/polymorfaResult.js @@ -11,7 +11,10 @@ function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-] function parseResponse(response) { const status = response.status; let body = response.body; - try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + try { if (typeof body === 'string') body = JSON.parse(body); } catch { + if (status >= 200 && status < 300) throw new IntegrationError('invalid_response', status); + body = undefined; + } if (status < 200 || status >= 300) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); diff --git a/make/modules/apiRequest/metadata.json b/make/modules/apiRequest/metadata.json index 76a9ca4..545688f 100644 --- a/make/modules/apiRequest/metadata.json +++ b/make/modules/apiRequest/metadata.json @@ -1,6 +1,6 @@ { "label": "Make an API call", - "description": "Performs an arbitrary authorized API call.", + "description": "Calls an allowlisted public operation on the Polymorfa API.", "connection": "project", "type": "action" } diff --git a/make/webhooks/callEnded/attach.json b/make/webhooks/callEnded/attach.json index 8eed788..57c8d3a 100644 --- a/make/webhooks/callEnded/attach.json +++ b/make/webhooks/callEnded/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}", + "activationId": "{{uuid}}" }, "data": { "externalHookId": "{{temp.ownedId}}", @@ -22,7 +23,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key')}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key'); temp.activationId)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/callEnded/detach.json b/make/webhooks/callEnded/detach.json index c8b76b5..433fe04 100644 --- a/make/webhooks/callEnded/detach.json +++ b/make/webhooks/callEnded/detach.json @@ -1,16 +1,18 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "GET", + "qs": { + "limit": 100 + }, "response": { "temp": { - "gone": "{{statusCode = 404}}" - }, - "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded'))}}" + "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded')}}" + } } }, { - "condition": "{{!temp.gone}}", + "condition": "{{temp.ownedPresent}}", "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "DELETE", "headers": { diff --git a/make/webhooks/messageReceived/attach.json b/make/webhooks/messageReceived/attach.json index a496ca1..aeee386 100644 --- a/make/webhooks/messageReceived/attach.json +++ b/make/webhooks/messageReceived/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}", + "activationId": "{{uuid}}" }, "data": { "externalHookId": "{{temp.ownedId}}", @@ -22,7 +23,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key')}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key'); temp.activationId)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/messageReceived/detach.json b/make/webhooks/messageReceived/detach.json index e6e7f3a..8fbd3f5 100644 --- a/make/webhooks/messageReceived/detach.json +++ b/make/webhooks/messageReceived/detach.json @@ -1,16 +1,18 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "GET", + "qs": { + "limit": 100 + }, "response": { "temp": { - "gone": "{{statusCode = 404}}" - }, - "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived'))}}" + "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived')}}" + } } }, { - "condition": "{{!temp.gone}}", + "condition": "{{temp.ownedPresent}}", "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "DELETE", "headers": { diff --git a/make/webhooks/messageState/attach.json b/make/webhooks/messageState/attach.json index 4678bc8..035b383 100644 --- a/make/webhooks/messageState/attach.json +++ b/make/webhooks/messageState/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}", + "activationId": "{{uuid}}" }, "data": { "externalHookId": "{{temp.ownedId}}", @@ -22,7 +23,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key')}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key'); temp.activationId)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/messageState/detach.json b/make/webhooks/messageState/detach.json index 26eb96d..016eb17 100644 --- a/make/webhooks/messageState/detach.json +++ b/make/webhooks/messageState/detach.json @@ -1,16 +1,18 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "GET", + "qs": { + "limit": 100 + }, "response": { "temp": { - "gone": "{{statusCode = 404}}" - }, - "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState'))}}" + "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState')}}" + } } }, { - "condition": "{{!temp.gone}}", + "condition": "{{temp.ownedPresent}}", "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "DELETE", "headers": { diff --git a/make/webhooks/numberState/attach.json b/make/webhooks/numberState/attach.json index e50ec09..382cf22 100644 --- a/make/webhooks/numberState/attach.json +++ b/make/webhooks/numberState/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}", + "activationId": "{{uuid}}" }, "data": { "externalHookId": "{{temp.ownedId}}", @@ -22,7 +23,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "integration-create-{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key')}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key'); temp.activationId)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/numberState/detach.json b/make/webhooks/numberState/detach.json index 167b5fc..3e46021 100644 --- a/make/webhooks/numberState/detach.json +++ b/make/webhooks/numberState/detach.json @@ -1,16 +1,18 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "GET", + "qs": { + "limit": 100 + }, "response": { "temp": { - "gone": "{{statusCode = 404}}" - }, - "valid": "{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState'))}}" + "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState')}}" + } } }, { - "condition": "{{!temp.gone}}", + "condition": "{{temp.ownedPresent}}", "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "DELETE", "headers": { diff --git a/n8n/README.md b/n8n/README.md index b3e1bd3..846bb24 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -30,6 +30,11 @@ does not contain the original message ID. Media accepts HTTPS URLs, base64 or a named n8n binary input field. The connector caps decoded bytes at 4 MiB. Provider/API bounds can be lower. IDs remain strings. +With **Continue On Fail** enabled, successful batch items keep their results and +failed items return an error paired to the original input. Later items continue. +Use the saved write identity when handling an error; do not replay successful +items with new keys. + Give each trigger a unique, stable **Subscription Key**. Webhook subscriptions are project-wide. Filtering a workflow after receipt does not prevent disclosure to n8n. Dedupe by event `id` before downstream effects; repeated delivery is diff --git a/n8n/nodes/Polymorfa/Polymorfa.node.ts b/n8n/nodes/Polymorfa/Polymorfa.node.ts index ed8d30d..0939889 100644 --- a/n8n/nodes/Polymorfa/Polymorfa.node.ts +++ b/n8n/nodes/Polymorfa/Polymorfa.node.ts @@ -52,6 +52,10 @@ export class Polymorfa implements INodeType { const data = await execute(action, p, auth, transport(this), 'n8n'); result.push({ json: data, pairedItem: { item: i } }); } catch (error) { + if (this.continueOnFail()) { + result.push({ json: { error: error instanceof Error ? error.message : 'Polymorfa request failed.' }, pairedItem: { item: i } }); + continue; + } throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i }); } } diff --git a/n8n/shared/core.js b/n8n/shared/core.js index 7b0b97f..6d3f209 100644 --- a/n8n/shared/core.js +++ b/n8n/shared/core.js @@ -112,7 +112,10 @@ function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-] function parseResponse(response) { const status = response.status; let body = response.body; - try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + try { if (typeof body === 'string') body = JSON.parse(body); } catch { + if (status >= 200 && status < 300) throw new IntegrationError('invalid_response', status); + body = undefined; + } if (status < 200 || status >= 300) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); diff --git a/n8n/shared/hooks.js b/n8n/shared/hooks.js index 38794b9..577f987 100644 --- a/n8n/shared/hooks.js +++ b/n8n/shared/hooks.js @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.js'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.js'); function identity(auth, group, subscriptionKey) { @@ -9,18 +9,21 @@ function identity(auth, group, subscriptionKey) { required(subscriptionKey, 'subscription_key'); if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); - const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); - return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; + const derive = label => createHmac('sha256', required(auth.apiKey, 'project_token')).update(`${label}:${scope}`).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${derive('marker').slice(0, 24)}`, value: derive('receiver'), key: derive('create') }; } function callback(url) { let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); - return url; + return u.href; +} +function sameCallback(a, b) { + try { return callback(a) === callback(b); } catch { return false; } } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; - return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && sameCallback(hook.url, url) && hook.format === 'native' && hook.enabled === true && Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); } @@ -36,10 +39,10 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); - if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + if (r.data.some(h => !sameCallback(h.url, url) && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); for (const hook of r.data) { const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); - if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if ((marked || sameCallback(hook.url, url)) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); } if (!r.page?.hasMore) { @@ -52,17 +55,19 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { throw new IntegrationError('subscription_inventory_requires_reconciliation'); } async function subscribe(auth, url, group, subscriptionKey, transport, provider) { - callback(url); + url = callback(url); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + // One create invocation owns its key. A later activation must not replay a deleted hook. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); return record(r.data.webhook); - } catch { + } catch (error) { + if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. const recovered = await find(auth, url, group, subscriptionKey, transport, provider); if (recovered) return record(recovered); @@ -89,4 +94,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/package.json b/package.json index 26a4323..ebbeb55 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "check:upstream": "node scripts/check-upstream.mjs", "check:sdk": "node scripts/verify-sdk.mjs", "build": "npm run generate && npm run build --workspace n8n-nodes-polymorfa", + "pretest": "npm run build", "test": "node --test --test-reporter=spec tests/*.test.cjs tests/*.test.mjs", - "check": "npm run check:contracts && npm run check:generated && npm run build && npm test", + "check": "npm run check:contracts && npm run check:generated && npm test", "validate:zapier": "npm run validate --workspace polymorfa-zapier", "make:access": "node scripts/make-access.mjs" }, diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs index 7b0b97f..6d3f209 100644 --- a/pipedream/common/core.cjs +++ b/pipedream/common/core.cjs @@ -112,7 +112,10 @@ function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-] function parseResponse(response) { const status = response.status; let body = response.body; - try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + try { if (typeof body === 'string') body = JSON.parse(body); } catch { + if (status >= 200 && status < 300) throw new IntegrationError('invalid_response', status); + body = undefined; + } if (status < 200 || status >= 300) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); diff --git a/pipedream/common/hooks.cjs b/pipedream/common/hooks.cjs index 0ddb3f9..322aa0c 100644 --- a/pipedream/common/hooks.cjs +++ b/pipedream/common/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -9,18 +9,21 @@ function identity(auth, group, subscriptionKey) { required(subscriptionKey, 'subscription_key'); if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); - const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); - return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; + const derive = label => createHmac('sha256', required(auth.apiKey, 'project_token')).update(`${label}:${scope}`).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${derive('marker').slice(0, 24)}`, value: derive('receiver'), key: derive('create') }; } function callback(url) { let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); - return url; + return u.href; +} +function sameCallback(a, b) { + try { return callback(a) === callback(b); } catch { return false; } } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; - return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && sameCallback(hook.url, url) && hook.format === 'native' && hook.enabled === true && Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); } @@ -36,10 +39,10 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); - if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + if (r.data.some(h => !sameCallback(h.url, url) && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); for (const hook of r.data) { const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); - if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if ((marked || sameCallback(hook.url, url)) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); } if (!r.page?.hasMore) { @@ -52,17 +55,19 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { throw new IntegrationError('subscription_inventory_requires_reconciliation'); } async function subscribe(auth, url, group, subscriptionKey, transport, provider) { - callback(url); + url = callback(url); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + // One create invocation owns its key. A later activation must not replay a deleted hook. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); return record(r.data.webhook); - } catch { + } catch (error) { + if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. const recovered = await find(auth, url, group, subscriptionKey, transport, provider); if (recovered) return record(recovered); @@ -89,4 +94,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/recipes/n8n/order-notification.json b/recipes/n8n/order-notification.json index 708c3c3..6b63a86 100644 --- a/recipes/n8n/order-notification.json +++ b/recipes/n8n/order-notification.json @@ -2,9 +2,75 @@ "name": "Polymorfa order notification (configure before use)", "active": false, "nodes": [ - { "id": "recipe-input", "name": "Replace with authorized order trigger", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [0, 0], "parameters": {} }, - { "id": "notify-order", "name": "Send order notification", "type": "n8n-nodes-polymorfa.polymorfa", "typeVersion": 1, "position": [260, 0], "parameters": { "operation": "sendText", "session": "={{$json.numberId}}", "recipientType": "phoneNumber", "recipient": "={{$json.recipient}}", "text": "={{$json.notificationText}}", "idempotencyKey": "={{'order-notification:' + $json.notificationId}}", "writeCreatedAt": "={{$json.notificationStartedAt}}" } } + { + "id": "recipe-input", + "name": "Replace with authorized order trigger", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 0, + 0 + ], + "parameters": {} + }, + { + "id": "validate-order", + "name": "Validate saved notification ID", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 260, + 0 + ], + "parameters": { + "jsCode": "return $input.all().map((item, index) => {\n if (typeof item.json.notificationId !== 'string' || !item.json.notificationId.trim()) {\n throw new Error('A saved non-empty notificationId is required before sending.');\n }\n return { ...item, pairedItem: { item: index } };\n});" + } + }, + { + "id": "notify-order", + "name": "Send order notification", + "type": "n8n-nodes-polymorfa.polymorfa", + "typeVersion": 1, + "position": [ + 520, + 0 + ], + "parameters": { + "operation": "sendText", + "session": "={{$json.numberId}}", + "recipientType": "phoneNumber", + "recipient": "={{$json.recipient}}", + "text": "={{$json.notificationText}}", + "idempotencyKey": "={{'order-notification:' + $json.notificationId}}", + "writeCreatedAt": "={{$json.notificationStartedAt}}" + } + } ], - "connections": { "Replace with authorized order trigger": { "main": [[{ "node": "Send order notification", "type": "main", "index": 0 }]] } }, - "settings": { "executionOrder": "v1" } + "connections": { + "Replace with authorized order trigger": { + "main": [ + [ + { + "node": "Validate saved notification ID", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validate saved notification ID": { + "main": [ + [ + { + "node": "Send order notification", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } } diff --git a/scripts/check-upstream.mjs b/scripts/check-upstream.mjs index d9dab60..c5fd91b 100644 --- a/scripts/check-upstream.mjs +++ b/scripts/check-upstream.mjs @@ -44,7 +44,7 @@ for (const [kind, pin] of Object.entries(source.specs)) { compare(local.components[section]?.[name], sdk.components?.[section]?.[name], `${kind}:${v.$ref}`); } Object.values(v).forEach(visit); - }; visit(a); + }; visit(a); visit(b); }; for (const item of Object.values(coverage.operations).filter(v => v.contract === kind)) compare(local.paths[item.path][item.method.toLowerCase()], sdk.paths?.[item.path]?.[item.method.toLowerCase()], `${item.method} ${item.path}`); } diff --git a/scripts/generate.mjs b/scripts/generate.mjs index ffe4b4b..93271e3 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -60,17 +60,22 @@ imlFunction('polymorfaAdvanced', 'p, auth', `${errorClass}\n${core.required}\n${ imlFunction('polymorfaRequest', 'action, p, auth', `${errorClass}\nconst ORIGIN = '${core.ORIGIN}', VERSION = '${core.VERSION}', MAX_WRITE_AGE = ${23 * 60 * 60 * 1000};\n${core.required}\n${core.segment}\n${core.headers}\n${core.writeHeaders}\n${core.buildRequest.toString().replace('messageBody(action, p)', 'iml.polymorfaBody(action, p)').replace('advanced(p, auth)', 'iml.polymorfaAdvanced(p, auth)')}`, "return buildRequest(action, p, auth, 'make');"); imlFunction('polymorfaResult', 'status, headers, body', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body });'); imlFunction('polymorfaSegment', 'value', `${errorClass}\n${core.required}\n${core.segment}`, "return segment(value, 'resource_id');"); -imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst secret = iml.sha256(scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + secret.slice(0, 24), value: secret, key: secret };`); +imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst derive = label => iml.sha256(label + ':' + scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + derive('marker').slice(0, 24), value: derive('receiver'), key: derive('create') };`); +imlFunction('polymorfaCreateKey', 'key, nonce', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9-]{16,64}$/.test(nonce)) throw new Error('Missing webhook activation identity.');\nreturn 'integration-create-' + key + '-' + nonce;`); +imlFunction('polymorfaCallback', 'value', '', `if (typeof value !== 'string' || /[\\s\\\\\\x00-\\x1f\\x7f]/.test(value)) throw new Error('Invalid callback.');\nconst parts = value.match(/^https:\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?$/i);\nif (!parts || parts[1].includes('@')) throw new Error('Invalid callback.');\nreturn 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || '');`); +imlFunction('polymorfaError', 'status, headers, body', '', `try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; }`); imlFunction('polymorfaReceive', 'expectedName, expectedValue, group, headers, body', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase());\nif (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false;\nreturn !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload));`); -imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => h.url !== url && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && h.url === url && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (h.url === url || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); -imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || h.url !== ownedUrl || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); +imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\nconst same = h => iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url);`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (same(h) || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); +imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || iml.polymorfaCallback(h.url) !== iml.polymorfaCallback(ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); +imlFunction('polymorfaDetachHook', 'body, id, projectId, ownedUrl, markerName, group', '', `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nconst matches = body.data.filter(h => h.id === id);\nif (matches.length > 1) throw new Error('Duplicate subscription IDs need reconciliation.');\nif (!matches.length) return false;\nreturn iml.polymorfaOwnedHook({ data: matches[0] }, projectId, ownedUrl, markerName, group);`); emit('make/app.json', { name: 'polymorfa', label: 'Polymorfa', version: 1, theme: '#7657ff', language: 'en', description: 'Send messages and receive project events with Polymorfa.' }); -emit('make/base.json', { baseUrl: core.ORIGIN, headers: { Authorization: 'Bearer {{connection.apiKey}}', 'Polymorfa-Version': core.VERSION, 'User-Agent': 'polymorfa-make/0.1.0' }, log: { sanitize: ['request.headers.authorization', 'request.body.headers', 'response.body.data.secret'] }, response: { error: { message: '[{{statusCode}}] Polymorfa {{body.error.code}}' } } }); +const makeError = '{{polymorfaError(statusCode; headers; body)}}'; +emit('make/base.json', { baseUrl: core.ORIGIN, headers: { Authorization: 'Bearer {{connection.apiKey}}', 'Polymorfa-Version': core.VERSION, 'User-Agent': 'polymorfa-make/0.1.0' }, log: { sanitize: ['request.headers.authorization', 'request.headers.Authorization', 'request.headers.idempotency-key', 'request.headers.Idempotency-Key', 'request.body.headers', 'response.body.data.secret', 'response.body.data.idempotency.key'] }, response: { error: { message: makeError, '409': { message: makeError }, '429': { type: 'RateLimitError', message: makeError } } } }); emit('make/connections/project/parameters.json', [{ name: 'apiKey', type: 'password', label: 'Project Token', required: true }, { name: 'projectId', type: 'text', label: 'Project ID', required: true }]); emit('make/connections/project/communication.json', { url: '/platform/sessions', method: 'GET', qs: { projectId: '{{parameters.projectId}}' }, headers: { Authorization: 'Bearer {{parameters.apiKey}}', 'Polymorfa-Version': core.VERSION }, response: { valid: '{{statusCode = 200}}', metadata: { type: 'text', value: '{{parameters.projectId}}' } }, log: { sanitize: ['request.headers.authorization'] } }); for (const [key, action] of Object.entries(actions)) { - emit(`make/modules/${key}/metadata.json`, { label: key === 'apiRequest' ? 'Make an API call' : action.label, description: key === 'apiRequest' ? 'Performs an arbitrary authorized API call.' : action.description, connection: 'project', type: 'action' }); + emit(`make/modules/${key}/metadata.json`, { label: key === 'apiRequest' ? 'Make an API call' : action.label, description: key === 'apiRequest' ? 'Calls an allowlisted public operation on the Polymorfa API.' : action.description, connection: 'project', type: 'action' }); emit(`make/modules/${key}/parameters.json`, action.fields.map(name => { const f = fields[name]; return { name, type: f.options ? 'select' : 'text', label: f.label, required: !!f.required || (key !== 'apiRequest' && ['idempotencyKey', 'writeCreatedAt'].includes(name)), ...(f.default ? { default: f.default } : {}), ...(f.help ? { help: f.help } : {}), ...(f.options ? { options: Object.entries(f.options).map(([value, label]) => ({ value, label })) } : {}) }; @@ -99,12 +104,12 @@ for (const [key, e] of Object.entries(events)) { const marker = `polymorfaHook(connection; '${key}'; parameters.subscriptionKey)`; const markerField = field => `{{get(${marker}; '${field}')}}`; emit(`make/webhooks/${key}/attach.json`, [ - { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}` }, data: { externalHookId: '{{temp.ownedId}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, - { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `integration-create-${markerField('key')}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { data: { externalHookId: '{{body.data.webhook.id}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}`, activationId: '{{uuid}}' }, data: { externalHookId: '{{temp.ownedId}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, + { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); temp.activationId)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { data: { externalHookId: '{{body.data.webhook.id}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, ]); emit(`make/webhooks/${key}/detach.json`, [ - { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'GET', response: { temp: { gone: '{{statusCode = 404}}' }, valid: `{{if(statusCode = 404; true; polymorfaOwnedHook(body; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}'))}}` } }, - { condition: '{{!temp.gone}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'DELETE', headers: { 'Idempotency-Key': 'integration-delete-{{webhook.externalHookId}}' } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedPresent: `{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}')}}` } } }, + { condition: '{{temp.ownedPresent}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'DELETE', headers: { 'Idempotency-Key': 'integration-delete-{{webhook.externalHookId}}' } }, ]); const valid = `polymorfaReceive(parameters.receiverName; parameters.receiverValue; '${key}'; headers; body)`; emit(`make/webhooks/${key}/communication.json`, { condition: `{{${valid}}}`, respond: { status: `{{if(${valid}; 200; 401)}}`, type: 'json', body: {} }, output: { id: '{{body.id}}', session: '{{body.session}}', timestamp: '{{body.timestamp}}', event: '{{body.event}}', payload: '{{body.payload}}' } }); diff --git a/scripts/release-gate.mjs b/scripts/release-gate.mjs index 8a01ea7..1fccb98 100644 --- a/scripts/release-gate.mjs +++ b/scripts/release-gate.mjs @@ -1,11 +1,16 @@ import { readFileSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; import assert from 'node:assert/strict'; +import { validateReleaseIdentity } from './release-policy.mjs'; const pkg = JSON.parse(readFileSync('n8n/package.json')); assert.equal(process.env.RELEASE_VERSION, pkg.version, 'Dispatch version must match the reviewed package.'); const receipt = JSON.parse(readFileSync(`releases/${pkg.version}.json`)); for (const key of ['reviewedSource', 'apiProductionAcceptance', 'n8nRuntimeAcceptance', 'publisherOwnership', 'publicationAuthorization']) assert.ok(typeof receipt[key] === 'string' && receipt[key].length > 10, `Missing ${key} receipt. Source completion is not publication authority.`); assert.match(receipt.reviewedSource, /^[a-f0-9]{40}$/); +validateReleaseIdentity({ ref: process.env.RELEASE_REF, source: process.env.RELEASE_SOURCE, + checkedOut: execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(), + reviewed: process.env.RELEASE_REVIEWED_SOURCE, receiptReviewed: receipt.reviewedSource }); +execFileSync('git', ['merge-base', '--is-ancestor', receipt.reviewedSource, process.env.RELEASE_SOURCE]); // Receipt-only changes may follow the reviewed candidate; executable source must match. execFileSync('git', ['diff', '--exit-code', receipt.reviewedSource, 'HEAD', '--', 'shared', 'n8n', 'contracts', 'scripts', 'fixtures', 'package.json', 'package-lock.json', '.github/workflows/publish-n8n.yml'], { stdio: 'inherit' }); console.log('Required publication receipts are recorded. Protected environment approval and npm trusted publisher configuration remain required.'); diff --git a/scripts/release-policy.mjs b/scripts/release-policy.mjs new file mode 100644 index 0000000..ad40027 --- /dev/null +++ b/scripts/release-policy.mjs @@ -0,0 +1,9 @@ +import assert from 'node:assert/strict'; + +export function validateReleaseIdentity({ ref, source, checkedOut, reviewed, receiptReviewed }) { + assert.equal(ref, 'refs/heads/main', 'Publication is restricted to main.'); + assert.match(source || '', /^[a-f0-9]{40}$/, 'Dispatch source must be immutable.'); + assert.equal(checkedOut, source, 'Checkout must match the dispatched main commit.'); + assert.match(reviewed || '', /^[a-f0-9]{40}$/, 'Reviewed source must be immutable.'); + assert.equal(reviewed, receiptReviewed, 'Dispatch approval must bind the exact reviewed source receipt.'); +} diff --git a/shared/core.cjs b/shared/core.cjs index 95371a5..078feee 100644 --- a/shared/core.cjs +++ b/shared/core.cjs @@ -111,7 +111,10 @@ function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-] function parseResponse(response) { const status = response.status; let body = response.body; - try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + try { if (typeof body === 'string') body = JSON.parse(body); } catch { + if (status >= 200 && status < 300) throw new IntegrationError('invalid_response', status); + body = undefined; + } if (status < 200 || status >= 300) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); diff --git a/shared/hooks.cjs b/shared/hooks.cjs index eafa6cd..670056c 100644 --- a/shared/hooks.cjs +++ b/shared/hooks.cjs @@ -1,6 +1,6 @@ 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -8,18 +8,21 @@ function identity(auth, group, subscriptionKey) { required(subscriptionKey, 'subscription_key'); if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); - const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); - return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; + const derive = label => createHmac('sha256', required(auth.apiKey, 'project_token')).update(`${label}:${scope}`).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${derive('marker').slice(0, 24)}`, value: derive('receiver'), key: derive('create') }; } function callback(url) { let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); - return url; + return u.href; +} +function sameCallback(a, b) { + try { return callback(a) === callback(b); } catch { return false; } } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; - return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && sameCallback(hook.url, url) && hook.format === 'native' && hook.enabled === true && Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); } @@ -35,10 +38,10 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); - if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + if (r.data.some(h => !sameCallback(h.url, url) && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); for (const hook of r.data) { const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); - if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if ((marked || sameCallback(hook.url, url)) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); } if (!r.page?.hasMore) { @@ -51,17 +54,19 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { throw new IntegrationError('subscription_inventory_requires_reconciliation'); } async function subscribe(auth, url, group, subscriptionKey, transport, provider) { - callback(url); + url = callback(url); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + // One create invocation owns its key. A later activation must not replay a deleted hook. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); return record(r.data.webhook); - } catch { + } catch (error) { + if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. const recovered = await find(auth, url, group, subscriptionKey, transport, provider); if (recovered) return record(recovered); @@ -88,4 +93,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/tests/core.test.cjs b/tests/core.test.cjs index ced77c5..628498d 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -11,6 +11,11 @@ ajv.addSchema(spec, 'messaging'); const now = Date.parse('2026-11-01T12:00:00Z'); const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; const input = { session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'An order is ready.', idempotencyKey: 'order-123-notification-1', writeCreatedAt: '2026-11-01T11:59:00Z' }; + +test('non-JSON gateway failures retain uncertain-write recovery and safe retry metadata', () => { + assert.throws(() => core.parseResponse({ status: 502, headers: { 'retry-after': '8' }, body: 'upstream unavailable' }), e => e.code === 'outcome_unknown' && e.retryAfter === 8 && /Reconcile/.test(e.message)); + assert.throws(() => core.parseResponse({ status: 200, body: 'not JSON' }), e => e.code === 'invalid_response'); +}); test('text/reply preserves opaque IDs, version, credential and logical-write identity', () => { const r = core.buildRequest('sendText', { ...input, quotedMessageId: '9007199254740993124' }, auth, 'test', now); assert.equal(r.url, 'https://api.polymorfa.com/messaging/739182640518203/messages/send'); diff --git a/tests/hooks.test.cjs b/tests/hooks.test.cjs index a37453d..49d576b 100644 --- a/tests/hooks.test.cjs +++ b/tests/hooks.test.cjs @@ -89,3 +89,39 @@ test('editor samples match their public event schemas', () => { assert.ok(validate(sample), `${group}: ${JSON.stringify(validate.errors)}`); } }); + +test('public marker and create keys do not reveal receiver verifier material', () => { + const marker = hooks.identity(auth, 'messageReceived', key); + assert.notEqual(marker.key, marker.value); + assert.ok(!marker.name.includes(marker.value.slice(0, 24))); +}); + +test('reactivating a deleted hook uses a new create identity', async () => { + const s = server(); const transport = s.request.bind(s); + const first = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'); + await hooks.unsubscribe(auth, first.id, url, 'messageReceived', key, transport, 'test'); + await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'); + const requests = s.calls.filter(r => r.method === 'POST'); + assert.equal(requests.length, 2); + assert.notEqual(requests[0].headers['Idempotency-Key'], requests[1].headers['Idempotency-Key']); + assert.ok(requests.every(r => !r.headers['Idempotency-Key'].includes(hooks.identity(auth, 'messageReceived', key).value))); +}); + +test('callback normalization preserves ownership across activation and cleanup', async () => { + const s = server(); const transport = s.request.bind(s); + const first = await hooks.subscribe(auth, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test'); + assert.equal(s.rows[0].url, 'https://example.com/'); + assert.deepEqual(await hooks.subscribe(auth, 'https://example.com/', 'messageReceived', key, transport, 'test'), first); + await hooks.unsubscribe(auth, first.id, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test'); + assert.equal(s.rows.length, 0); + assert.equal(hooks.sameCallback(url + '?key=A', url + '?key=a'), false); +}); + +for (const status of [403, 422, 429]) test(`definite subscription rejection ${status} preserves its error and retry metadata`, async () => { + let reads = 0; + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, async r => { + if (r.method === 'GET') { reads++; return { status: 200, body: { data: [], page: { hasMore: false } } }; } + return { status, headers: { 'retry-after': '17', 'x-request-id': 'safe-request' }, body: { error: { code: 'scope_denied' } } }; + }, 'test'), e => e.status === status && e.code === 'scope_denied' && e.retryAfter === 17 && e.requestId === 'safe-request'); + assert.equal(reads, 1); +}); diff --git a/tests/make.test.cjs b/tests/make.test.cjs index 0d5e252..77d1224 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -39,3 +39,33 @@ test('Make preserves an operator-disabled owned hook', () => { const row = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://example.com', enabled: false, format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; assert.throws(() => iml.polymorfaFindHook({ data: [row], page: { hasMore: false } }, auth, row.url, 'messageReceived', 'key')); }); + +test('Make activation keys are independent of receiver secrets and change between activations', () => { + const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); + const first = iml.polymorfaCreateKey(marker.key, '11111111-1111-4111-8111-111111111111'); + const second = iml.polymorfaCreateKey(marker.key, '22222222-2222-4222-8222-222222222222'); + assert.notEqual(first, second); assert.ok(!first.includes(marker.value)); + assert.throws(() => iml.polymorfaCreateKey(marker.key, '')); + const base = JSON.parse(readFileSync('make/base.json')); + for (const path of ['request.headers.idempotency-key', 'request.body.headers', 'response.body.data.idempotency.key']) assert.ok(base.log.sanitize.includes(path)); +}); + +test('Make detach only deletes owned IDs in a complete successful inventory', () => { + const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); + const row = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://EXAMPLE.COM:443', format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; + const args = ['owned', auth.projectId, 'https://example.com/', marker.name, 'messageReceived']; + assert.equal(iml.polymorfaDetachHook({ data: [], page: { hasMore: false } }, ...args), false); + assert.equal(iml.polymorfaDetachHook({ data: [row], page: { hasMore: false } }, ...args), true); + assert.throws(() => iml.polymorfaDetachHook({ data: [], page: { hasMore: true } }, ...args)); + assert.throws(() => iml.polymorfaDetachHook({ data: [{ ...row, projectId: 'other-project' }], page: { hasMore: false } }, ...args)); + const detach = JSON.parse(readFileSync('make/webhooks/messageReceived/detach.json')); + assert.ok(detach[0].url.endsWith('/webhooks')); assert.equal(detach[1].condition, '{{temp.ownedPresent}}'); +}); + +test('Make non-2xx error handlers retain recovery and rate-limit guidance without response secrets', () => { + assert.match(iml.polymorfaError(409, {}, { error: { code: 'idempotency_completed', message: auth.apiKey } }), /Recover its original result/); + assert.match(iml.polymorfaError(429, { 'retry-after': '19' }, { error: { code: 'rate_limited' } }), /19 seconds/); + assert.match(iml.polymorfaError(502, {}, 'gateway error'), /Reconcile/); + const error = JSON.parse(readFileSync('make/base.json')).response.error; + assert.ok(error['409'].message.includes('polymorfaError')); assert.equal(error['429'].type, 'RateLimitError'); assert.equal(error['5xx'], undefined); +}); diff --git a/tests/native.test.cjs b/tests/native.test.cjs index 7736c62..23b54b1 100644 --- a/tests/native.test.cjs +++ b/tests/native.test.cjs @@ -48,3 +48,27 @@ test('Zapier native hook uses stable event IDs and synthetic setup samples witho assert.equal((await operation.performList(z, bundle))[0].id, sample.id); assert.equal(requests, 0); await assert.rejects(operation.perform(z, { ...bundle, cleanedRequest: { headers: {}, content: sample } })); }); + +test('n8n continue-on-fail retains successful write results and continues later items', async () => { + let requests = 0; + const items = [{ json: {} }, { json: {} }, { json: {} }]; + const context = { getCredentials: async () => auth, getInputData: () => items, continueOnFail: () => true, + getNodeParameter: (key, i, fallback) => key === 'recipient' && i === 1 ? 'invalid-conversation-id' : input[key] ?? fallback, + getNode: () => ({ name: 'test', typeVersion: 1 }), + helpers: { httpRequest: async () => ({ statusCode: 200, headers: {}, body: { data: { id: String(++requests) } } }) } }; + const [result] = await new Polymorfa().execute.call(context); + assert.equal(requests, 2); assert.equal(result.length, 3); + assert.equal(result[0].json.data.id, '1'); assert.equal(result[2].json.data.id, '2'); + assert.match(result[1].json.error, /invalid_conversation_id/); assert.deepEqual(result[1].pairedItem, { item: 1 }); + context.continueOnFail = () => false; + await assert.rejects(new Polymorfa().execute.call(context)); +}); + +test('Zapier consumes native Headers and requires write identity in its editor', async () => { + const z = { request: async () => ({ status: 429, headers: new Headers({ 'Retry-After': '12', 'X-Request-ID': 'safe-request' }), content: '{}' }) }; + await assert.rejects(zapier.creates.sendText.operation.perform(z, { authData: auth, inputData: input }), e => e.retryAfter === 12 && e.requestId === 'safe-request'); + for (const key of ['sendText', 'sendTemplate', 'sendMedia', 'react']) for (const name of ['idempotencyKey', 'writeCreatedAt']) { + assert.equal(zapier.creates[key].operation.inputFields.find(f => f.key === name).required, true); + } + assert.equal(zapier.creates.apiRequest.operation.inputFields.find(f => f.key === 'idempotencyKey').required, false); +}); diff --git a/tests/recipes.test.cjs b/tests/recipes.test.cjs new file mode 100644 index 0000000..594c3ce --- /dev/null +++ b/tests/recipes.test.cjs @@ -0,0 +1,14 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const vm = require('node:vm'); +const recipe = require('../recipes/n8n/order-notification.json'); + +test('order notification rejects an absent business identity before the send node', () => { + const validate = recipe.nodes.find(n => n.id === 'validate-order'); + assert.equal(recipe.connections['Replace with authorized order trigger'].main[0][0].node, validate.name); + assert.equal(recipe.connections[validate.name].main[0][0].node, 'Send order notification'); + const run = item => vm.runInNewContext(`(function () { ${validate.parameters.jsCode} })()`, { $input: { all: () => [{ json: item }] } }); + for (const notificationId of [undefined, null, '', ' ', 0]) assert.throws(() => run({ notificationId })); + assert.equal(run({ notificationId: 'saved-order-1' })[0].json.notificationId, 'saved-order-1'); +}); diff --git a/tests/release-policy.test.mjs b/tests/release-policy.test.mjs new file mode 100644 index 0000000..bbe27b9 --- /dev/null +++ b/tests/release-policy.test.mjs @@ -0,0 +1,11 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { validateReleaseIdentity } from '../scripts/release-policy.mjs'; + +test('publication requires main and exact immutable dispatch, checkout and review identities', () => { + const valid = { ref: 'refs/heads/main', source: 'a'.repeat(40), checkedOut: 'a'.repeat(40), reviewed: 'b'.repeat(40), receiptReviewed: 'b'.repeat(40) }; + assert.doesNotThrow(() => validateReleaseIdentity(valid)); + for (const delta of [{ ref: 'refs/heads/unreviewed' }, { source: 'main' }, { checkedOut: 'c'.repeat(40) }, { reviewed: 'main' }, { receiptReviewed: 'd'.repeat(40) }]) { + assert.throws(() => validateReleaseIdentity({ ...valid, ...delta })); + } +}); diff --git a/tests/sdk-drift.test.mjs b/tests/sdk-drift.test.mjs new file mode 100644 index 0000000..6730d67 --- /dev/null +++ b/tests/sdk-drift.test.mjs @@ -0,0 +1,45 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; + +test('SDK drift detects a changed nested component referenced only by the SDK', t => { + const parent = join(homedir(), '.polymorfa-agent-work', 'integration-sdk-drift-tests'); + mkdirSync(parent, { recursive: true }); + const root = mkdtempSync(join(parent, 'fixture-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + mkdirSync(join(root, 'contracts')); + const source = 'a'.repeat(40); + const ref = name => ({ $ref: '#/components/schemas/' + name }); + const localOperation = { requestBody: { content: { 'application/json': { schema: ref('Local') } } } }; + const sdkOperation = { requestBody: { content: { 'application/json': { schema: ref('Transport') } } } }; + const local = { paths: { '/messages': { post: localOperation } }, components: { schemas: { Local: { type: 'string' } } } }; + const sdk = { paths: { '/messages': { post: sdkOperation } }, components: { schemas: { Local: { type: 'string' }, Transport: { type: 'object', properties: { detail: ref('TransportDetail') } }, TransportDetail: { type: 'string' } } } }; + const canonical = value => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(k => [k, canonical(value[k])])) : value; + const hash = value => createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex'); + const fragments = { 'POST /messages': hash(sdkOperation) }; + for (const [name, schema] of Object.entries(sdk.components.schemas)) fragments['#/components/schemas/' + name] = hash(schema); + const save = (name, value) => writeFileSync(join(root, name), JSON.stringify(value)); + save('contracts/source.json', { sdk: { repository: 'example/sdk', source }, specs: { messaging: { snapshot: 'contracts/local.json', sdkSnapshotPath: 'contracts/sdk.json' } } }); + save('contracts/coverage.json', { operations: { send: { contract: 'messaging', path: '/messages', method: 'POST' } } }); + save('contracts/sdk-baseline.json', { fragments: { messaging: fragments } }); + save('contracts/local.json', local); + save('sdk.json', sdk); + writeFileSync(join(root, 'fetch-fixture.mjs'), `import { readFileSync } from 'node:fs'; +globalThis.fetch = async input => { + if (!String(input).endsWith('/${source}/contracts/sdk.json')) throw new Error('Unexpected fixture request'); + return { ok: true, json: async () => JSON.parse(readFileSync(new URL('./sdk.json', import.meta.url))) }; +};`); + const run = () => spawnSync(process.execPath, ['--import', join(root, 'fetch-fixture.mjs'), fileURLToPath(new URL('../scripts/check-upstream.mjs', import.meta.url)), '--pinned'], { cwd: root, encoding: 'utf8' }); + const unchanged = run(); + assert.equal(unchanged.status, 0, unchanged.stderr); + sdk.components.schemas.TransportDetail.type = 'integer'; + save('sdk.json', sdk); + const changed = run(); + assert.equal(changed.status, 1); + assert.match(changed.stderr, /SDK drift from reviewed baseline: messaging:#\/components\/schemas\/TransportDetail/); +}); diff --git a/zapier/index.js b/zapier/index.js index e9e0fc0..abbf00c 100644 --- a/zapier/index.js +++ b/zapier/index.js @@ -7,7 +7,8 @@ const hooks = require('./shared/hooks.cjs'); function transport(z) { return async r => { const response = await z.request({ method: r.method, url: r.url, headers: r.headers, params: r.query, body: r.body, redirect: 'manual', skipThrowForStatus: true }); - return { status: response.status, headers: response.headers, body: response.content }; + const headers = typeof response.headers?.entries === 'function' ? Object.fromEntries(response.headers.entries()) : response.headers; + return { status: response.status, headers, body: response.content }; }; } function field(name) { @@ -17,7 +18,7 @@ function field(name) { const creates = Object.fromEntries(Object.entries(actions).map(([key, action]) => [key, { key, noun: 'Result', display: { label: action.label, description: action.description }, operation: { - inputFields: action.fields.map(field), + inputFields: action.fields.map(name => ({ ...field(name), ...(key !== 'apiRequest' && ['idempotencyKey', 'writeCreatedAt'].includes(name) ? { required: true } : {}) })), async perform(z, bundle) { return core.execute(key, bundle.inputData, bundle.authData, transport(z), 'zapier'); }, sample: { data: { id: '739182640518203' } }, }, diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs index 7b0b97f..6d3f209 100644 --- a/zapier/shared/core.cjs +++ b/zapier/shared/core.cjs @@ -112,7 +112,10 @@ function safeCode(v, fallback) { return typeof v === 'string' && /^[a-zA-Z0-9_-] function parseResponse(response) { const status = response.status; let body = response.body; - try { if (typeof body === 'string') body = JSON.parse(body); } catch { throw new IntegrationError('invalid_response', status); } + try { if (typeof body === 'string') body = JSON.parse(body); } catch { + if (status >= 200 && status < 300) throw new IntegrationError('invalid_response', status); + body = undefined; + } if (status < 200 || status >= 300) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); diff --git a/zapier/shared/hooks.cjs b/zapier/shared/hooks.cjs index 0ddb3f9..322aa0c 100644 --- a/zapier/shared/hooks.cjs +++ b/zapier/shared/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -9,18 +9,21 @@ function identity(auth, group, subscriptionKey) { required(subscriptionKey, 'subscription_key'); if (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key'); const scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]); - const secret = createHmac('sha256', required(auth.apiKey, 'project_token')).update(scope).digest('hex'); - return { name: `X-Polymorfa-Integration-Token-${secret.slice(0, 24)}`, value: secret, key: secret }; + const derive = label => createHmac('sha256', required(auth.apiKey, 'project_token')).update(`${label}:${scope}`).digest('hex'); + return { name: `X-Polymorfa-Integration-Token-${derive('marker').slice(0, 24)}`, value: derive('receiver'), key: derive('create') }; } function callback(url) { let u; try { u = new URL(url); } catch { throw new IntegrationError('invalid_callback'); } if (u.protocol !== 'https:' || u.username || u.password || u.hash) throw new IntegrationError('invalid_callback'); - return url; + return u.href; +} +function sameCallback(a, b) { + try { return callback(a) === callback(b); } catch { return false; } } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; - return hook && hook.owner === 'project' && hook.projectId === auth.projectId && hook.url === url && hook.format === 'native' && hook.enabled === true && + return hook && hook.owner === 'project' && hook.projectId === auth.projectId && sameCallback(hook.url, url) && hook.format === 'native' && hook.enabled === true && Array.isArray(hook.headers) && hook.headers.some(h => h.name.toLowerCase() === marker) && Array.isArray(hook.eventTypes) && hook.eventTypes.length === types.length && types.every(t => hook.eventTypes.includes(t)); } @@ -36,10 +39,10 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { const r = await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, undefined, undefined, { limit: 100, ...(cursor ? { cursor } : {}) }); if (!Array.isArray(r.data)) throw new IntegrationError('invalid_subscription_response'); const marker = identity(auth, group, subscriptionKey).name.toLowerCase(); - if (r.data.some(h => h.url !== url && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); + if (r.data.some(h => !sameCallback(h.url, url) && h.headers?.some(v => v.name.toLowerCase() === marker))) throw new IntegrationError('subscription_key_already_in_use'); for (const hook of r.data) { const marked = hook.headers?.some(v => v.name.toLowerCase() === marker); - if ((marked || hook.url === url) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); + if ((marked || sameCallback(hook.url, url)) && !owns(hook, auth, url, group, subscriptionKey)) throw new IntegrationError('subscription_changed_requires_reconciliation'); if (owns(hook, auth, url, group, subscriptionKey)) matches.push(hook); } if (!r.page?.hasMore) { @@ -52,17 +55,19 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { throw new IntegrationError('subscription_inventory_requires_reconciliation'); } async function subscribe(auth, url, group, subscriptionKey, transport, provider) { - callback(url); + url = callback(url); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}`); + // One create invocation owns its key. A later activation must not replay a deleted hook. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); return record(r.data.webhook); - } catch { + } catch (error) { + if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. const recovered = await find(auth, url, group, subscriptionKey, transport, provider); if (recovered) return record(recovered); @@ -89,4 +94,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; From 9915045889955bb4ec9979825cb07d298b20863e Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 05:02:26 +0200 Subject: [PATCH 06/14] Preserve webhook creation generations and native edge cases --- README.md | 8 +++ make/functions/polymorfaBody.js | 7 +- make/functions/polymorfaCreateKey.js | 6 +- make/functions/polymorfaEnvelope.js | 17 +++++ make/functions/polymorfaLiveHook.js | 6 ++ make/modules/react/parameters.json | 4 +- make/webhooks/callEnded/attach.json | 25 +++++--- make/webhooks/callEnded/communication.json | 8 +-- make/webhooks/callEnded/parameters.json | 7 ++ make/webhooks/messageReceived/attach.json | 25 +++++--- .../messageReceived/communication.json | 8 +-- make/webhooks/messageReceived/parameters.json | 7 ++ make/webhooks/messageState/attach.json | 25 +++++--- make/webhooks/messageState/communication.json | 8 +-- make/webhooks/messageState/parameters.json | 7 ++ make/webhooks/numberState/attach.json | 25 +++++--- make/webhooks/numberState/communication.json | 8 +-- make/webhooks/numberState/parameters.json | 7 ++ n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts | 3 +- n8n/shared/catalog.js | 2 +- n8n/shared/core.js | 7 +- n8n/shared/hooks.js | 27 +++++--- pipedream/actions/react/react.mjs | 4 +- pipedream/common/catalog.cjs | 2 +- pipedream/common/core.cjs | 7 +- pipedream/common/hooks.cjs | 27 +++++--- pipedream/polymorfa.app.mjs | 2 +- pipedream/sources/callEnded/callEnded.mjs | 5 +- .../messageReceived/messageReceived.mjs | 5 +- .../sources/messageState/messageState.mjs | 5 +- pipedream/sources/numberState/numberState.mjs | 5 +- scripts/generate.mjs | 19 ++++-- shared/catalog.cjs | 2 +- shared/core.cjs | 7 +- shared/hooks.cjs | 27 +++++--- tests/core.test.cjs | 16 +++++ tests/hooks.test.cjs | 64 +++++++++++++------ tests/make.test.cjs | 17 +++++ tests/native.test.cjs | 19 ++++++ zapier/index.js | 24 +++++-- zapier/shared/catalog.cjs | 2 +- zapier/shared/core.cjs | 7 +- zapier/shared/hooks.cjs | 27 +++++--- 43 files changed, 368 insertions(+), 172 deletions(-) create mode 100644 make/functions/polymorfaEnvelope.js create mode 100644 make/functions/polymorfaLiveHook.js diff --git a/README.md b/README.md index 7d97f53..488a18a 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ a separate workflow engine or stores your third-party app connections. Documentation: [docs.polymorfa.com](https://docs.polymorfa.com). +Webhook triggers save two values in their native configuration. The subscription +key identifies the trigger and stays stable. The webhook creation key identifies +one registration attempt and stays the same across uncertain retries. Change the +creation key only after confirming that the previous registration was deleted. +Reusing a deleted registration's key fails reconciliation instead of reporting a +working trigger. An old or uncertain registration needs inventory reconciliation +before a new creation key is used. Native editor acceptance remains pending. + Build and maintenance: [MAINTAINING.md](MAINTAINING.md). Provider source: [n8n](n8n/README.md), [Make](make/README.md), [Zapier](zapier/README.md), [Pipedream](pipedream/README.md). diff --git a/make/functions/polymorfaBody.js b/make/functions/polymorfaBody.js index 14d15d8..131e6ff 100644 --- a/make/functions/polymorfaBody.js +++ b/make/functions/polymorfaBody.js @@ -42,7 +42,7 @@ function messageBody(action, p) { // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); - } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || p.media.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } const media = { [p.mediaSource]: p.media }; @@ -50,8 +50,9 @@ function messageBody(action, p) { if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); body.content = { [p.mediaType]: media }; } else if (action === 'react') { - if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); - body.id = p.messageId; body.reaction = p.reaction; + const reaction = p.reaction ?? ''; + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = reaction; } else throw new IntegrationError('unknown_action'); return body; } diff --git a/make/functions/polymorfaCreateKey.js b/make/functions/polymorfaCreateKey.js index 30dd374..5b1a567 100644 --- a/make/functions/polymorfaCreateKey.js +++ b/make/functions/polymorfaCreateKey.js @@ -1,5 +1,5 @@ -function polymorfaCreateKey(key, nonce) { +function polymorfaCreateKey(key, registrationKey) { -if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9-]{16,64}$/.test(nonce)) throw new Error('Missing webhook activation identity.'); -return 'integration-create-' + key + '-' + nonce; +if (!/^[a-f0-9]{64}$/.test(key) || typeof registrationKey !== 'string' || !registrationKey.trim() || registrationKey.length > 200) throw new Error('Missing webhook creation key.'); +return 'integration-create-' + iml.sha256(registrationKey, 'hex', key); } diff --git a/make/functions/polymorfaEnvelope.js b/make/functions/polymorfaEnvelope.js new file mode 100644 index 0000000..c096330 --- /dev/null +++ b/make/functions/polymorfaEnvelope.js @@ -0,0 +1,17 @@ +function polymorfaEnvelope(body, group) { +class IntegrationError extends Error { + constructor(code, status = 0, requestId, retryAfter) { + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); + this.name = 'IntegrationError'; this.code = code; this.status = status; + this.requestId = requestId; this.retryAfter = retryAfter; + } +} +const events = {"messageReceived":{"label":"New Message","description":"Triggers when a message is received.","types":["message.received"]},"messageState":{"label":"Message State Changed","description":"Triggers when a message is sent, acknowledged, failed or updated.","types":["message.sent","message.ack","message.failed","message.update"]},"numberState":{"label":"Number State Changed","description":"Triggers when a Number changes state, connects or logs out.","types":["session.status","session.connected","session.logged_out"]},"callEnded":{"label":"Call Ended","description":"Triggers when a call ends.","types":["call.ended"]}}; +function envelope(body, group) { + if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); + // Copy the defined envelope only. Never export arbitrary top-level transport fields. + return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; +} +return envelope(body, group); +} diff --git a/make/functions/polymorfaLiveHook.js b/make/functions/polymorfaLiveHook.js new file mode 100644 index 0000000..4d6a5e1 --- /dev/null +++ b/make/functions/polymorfaLiveHook.js @@ -0,0 +1,6 @@ +function polymorfaLiveHook(body, auth, url, group, key, expectedId) { + +const id = iml.polymorfaFindHook(body, auth, url, group, key); +if (!id || id !== expectedId) throw new Error('Subscription receipt needs reconciliation.'); +return id; +} diff --git a/make/modules/react/parameters.json b/make/modules/react/parameters.json index 0c43e6a..4d96b30 100644 --- a/make/modules/react/parameters.json +++ b/make/modules/react/parameters.json @@ -44,8 +44,8 @@ "name": "reaction", "type": "text", "label": "Reaction", - "required": true, - "help": "An emoji, or an empty string to remove the reaction." + "required": false, + "help": "An emoji. Leave empty to remove the reaction." }, { "name": "idempotencyKey", diff --git a/make/webhooks/callEnded/attach.json b/make/webhooks/callEnded/attach.json index 57c8d3a..c79ecc5 100644 --- a/make/webhooks/callEnded/attach.json +++ b/make/webhooks/callEnded/attach.json @@ -7,14 +7,7 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}", - "activationId": "{{uuid}}" - }, - "data": { - "externalHookId": "{{temp.ownedId}}", - "ownedUrl": "{{webhook.url}}", - "receiverName": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'name')}}", - "receiverValue": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'value')}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}" } } }, @@ -23,7 +16,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key'); temp.activationId)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" }, "body": { "url": "{{webhook.url}}", @@ -39,9 +32,21 @@ } ] }, + "response": { + "temp": { + "createdId": "{{body.data.webhook.id}}" + } + } + }, + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, "response": { "data": { - "externalHookId": "{{body.data.webhook.id}}", + "externalHookId": "{{polymorfaLiveHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}", "ownedUrl": "{{webhook.url}}", "receiverName": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'name')}}", "receiverValue": "{{get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'value')}}" diff --git a/make/webhooks/callEnded/communication.json b/make/webhooks/callEnded/communication.json index eaa0032..b0bee2c 100644 --- a/make/webhooks/callEnded/communication.json +++ b/make/webhooks/callEnded/communication.json @@ -5,11 +5,5 @@ "type": "json", "body": {} }, - "output": { - "id": "{{body.id}}", - "session": "{{body.session}}", - "timestamp": "{{body.timestamp}}", - "event": "{{body.event}}", - "payload": "{{body.payload}}" - } + "output": "{{polymorfaEnvelope(body; 'callEnded')}}" } diff --git a/make/webhooks/callEnded/parameters.json b/make/webhooks/callEnded/parameters.json index 73eeef1..2bd958b 100644 --- a/make/webhooks/callEnded/parameters.json +++ b/make/webhooks/callEnded/parameters.json @@ -5,5 +5,12 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." + }, + { + "name": "registrationKey", + "label": "Webhook Creation Key", + "type": "text", + "required": true, + "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/messageReceived/attach.json b/make/webhooks/messageReceived/attach.json index aeee386..89d4100 100644 --- a/make/webhooks/messageReceived/attach.json +++ b/make/webhooks/messageReceived/attach.json @@ -7,14 +7,7 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}", - "activationId": "{{uuid}}" - }, - "data": { - "externalHookId": "{{temp.ownedId}}", - "ownedUrl": "{{webhook.url}}", - "receiverName": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'name')}}", - "receiverValue": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'value')}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}" } } }, @@ -23,7 +16,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key'); temp.activationId)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" }, "body": { "url": "{{webhook.url}}", @@ -39,9 +32,21 @@ } ] }, + "response": { + "temp": { + "createdId": "{{body.data.webhook.id}}" + } + } + }, + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, "response": { "data": { - "externalHookId": "{{body.data.webhook.id}}", + "externalHookId": "{{polymorfaLiveHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}", "ownedUrl": "{{webhook.url}}", "receiverName": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'name')}}", "receiverValue": "{{get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'value')}}" diff --git a/make/webhooks/messageReceived/communication.json b/make/webhooks/messageReceived/communication.json index a5b891a..c67a31b 100644 --- a/make/webhooks/messageReceived/communication.json +++ b/make/webhooks/messageReceived/communication.json @@ -5,11 +5,5 @@ "type": "json", "body": {} }, - "output": { - "id": "{{body.id}}", - "session": "{{body.session}}", - "timestamp": "{{body.timestamp}}", - "event": "{{body.event}}", - "payload": "{{body.payload}}" - } + "output": "{{polymorfaEnvelope(body; 'messageReceived')}}" } diff --git a/make/webhooks/messageReceived/parameters.json b/make/webhooks/messageReceived/parameters.json index 73eeef1..2bd958b 100644 --- a/make/webhooks/messageReceived/parameters.json +++ b/make/webhooks/messageReceived/parameters.json @@ -5,5 +5,12 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." + }, + { + "name": "registrationKey", + "label": "Webhook Creation Key", + "type": "text", + "required": true, + "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/messageState/attach.json b/make/webhooks/messageState/attach.json index 035b383..2f6c909 100644 --- a/make/webhooks/messageState/attach.json +++ b/make/webhooks/messageState/attach.json @@ -7,14 +7,7 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}", - "activationId": "{{uuid}}" - }, - "data": { - "externalHookId": "{{temp.ownedId}}", - "ownedUrl": "{{webhook.url}}", - "receiverName": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'name')}}", - "receiverValue": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'value')}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}" } } }, @@ -23,7 +16,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key'); temp.activationId)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" }, "body": { "url": "{{webhook.url}}", @@ -42,9 +35,21 @@ } ] }, + "response": { + "temp": { + "createdId": "{{body.data.webhook.id}}" + } + } + }, + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, "response": { "data": { - "externalHookId": "{{body.data.webhook.id}}", + "externalHookId": "{{polymorfaLiveHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}", "ownedUrl": "{{webhook.url}}", "receiverName": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'name')}}", "receiverValue": "{{get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'value')}}" diff --git a/make/webhooks/messageState/communication.json b/make/webhooks/messageState/communication.json index ce93d70..bee2385 100644 --- a/make/webhooks/messageState/communication.json +++ b/make/webhooks/messageState/communication.json @@ -5,11 +5,5 @@ "type": "json", "body": {} }, - "output": { - "id": "{{body.id}}", - "session": "{{body.session}}", - "timestamp": "{{body.timestamp}}", - "event": "{{body.event}}", - "payload": "{{body.payload}}" - } + "output": "{{polymorfaEnvelope(body; 'messageState')}}" } diff --git a/make/webhooks/messageState/parameters.json b/make/webhooks/messageState/parameters.json index 73eeef1..2bd958b 100644 --- a/make/webhooks/messageState/parameters.json +++ b/make/webhooks/messageState/parameters.json @@ -5,5 +5,12 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." + }, + { + "name": "registrationKey", + "label": "Webhook Creation Key", + "type": "text", + "required": true, + "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/numberState/attach.json b/make/webhooks/numberState/attach.json index 382cf22..6f54116 100644 --- a/make/webhooks/numberState/attach.json +++ b/make/webhooks/numberState/attach.json @@ -7,14 +7,7 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}", - "activationId": "{{uuid}}" - }, - "data": { - "externalHookId": "{{temp.ownedId}}", - "ownedUrl": "{{webhook.url}}", - "receiverName": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'name')}}", - "receiverValue": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'value')}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}" } } }, @@ -23,7 +16,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key'); temp.activationId)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" }, "body": { "url": "{{webhook.url}}", @@ -41,9 +34,21 @@ } ] }, + "response": { + "temp": { + "createdId": "{{body.data.webhook.id}}" + } + } + }, + { + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "method": "GET", + "qs": { + "limit": 100 + }, "response": { "data": { - "externalHookId": "{{body.data.webhook.id}}", + "externalHookId": "{{polymorfaLiveHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}", "ownedUrl": "{{webhook.url}}", "receiverName": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'name')}}", "receiverValue": "{{get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'value')}}" diff --git a/make/webhooks/numberState/communication.json b/make/webhooks/numberState/communication.json index 901b226..7885eb3 100644 --- a/make/webhooks/numberState/communication.json +++ b/make/webhooks/numberState/communication.json @@ -5,11 +5,5 @@ "type": "json", "body": {} }, - "output": { - "id": "{{body.id}}", - "session": "{{body.session}}", - "timestamp": "{{body.timestamp}}", - "event": "{{body.event}}", - "payload": "{{body.payload}}" - } + "output": "{{polymorfaEnvelope(body; 'numberState')}}" } diff --git a/make/webhooks/numberState/parameters.json b/make/webhooks/numberState/parameters.json index 73eeef1..2bd958b 100644 --- a/make/webhooks/numberState/parameters.json +++ b/make/webhooks/numberState/parameters.json @@ -5,5 +5,12 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." + }, + { + "name": "registrationKey", + "label": "Webhook Creation Key", + "type": "text", + "required": true, + "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts index c53ab00..4d965ab 100644 --- a/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts +++ b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts @@ -14,6 +14,7 @@ export class PolymorfaTrigger implements INodeType { properties: [ { displayName: 'Event', name: 'eventGroup', type: 'options', default: 'messageReceived', options: Object.entries(events).map(([value, event]) => ({ name: event.label, value, description: event.description })) }, { displayName: 'Subscription Key', name: 'subscriptionKey', type: 'string', required: true, default: '', description: 'A unique stable label for this trigger. Keep it on reactivation; use a different value for another trigger.' }, + { displayName: 'Webhook Creation Key', name: 'registrationKey', type: 'string', required: true, default: '', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, { displayName: 'Events are project-wide. Filtering after receipt does not limit which Numbers disclose events to n8n.', name: 'scopeNotice', type: 'notice', default: '' }, ], }; @@ -27,7 +28,7 @@ export class PolymorfaTrigger implements INodeType { }, async create(this: IHookFunctions) { const auth = await this.getCredentials('polymorfaApi'); - const hook = await subscribe(auth, this.getNodeWebhookUrl('default')!, this.getNodeParameter('eventGroup') as string, this.getNodeParameter('subscriptionKey') as string, transport(this), 'n8n'); + const hook = await subscribe(auth, this.getNodeWebhookUrl('default')!, this.getNodeParameter('eventGroup') as string, this.getNodeParameter('subscriptionKey') as string, transport(this), 'n8n', this.getNodeParameter('registrationKey') as string); this.getWorkflowStaticData('node').subscription = hook; return true; }, async delete(this: IHookFunctions) { diff --git a/n8n/shared/catalog.js b/n8n/shared/catalog.js index e7ed5c9..eb14536 100644 --- a/n8n/shared/catalog.js +++ b/n8n/shared/catalog.js @@ -26,7 +26,7 @@ const fields = { caption: { label: 'Caption' }, filename: { label: 'Filename', help: 'Used for file messages.' }, messageId: { label: 'Message ID', required: true }, - reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + reaction: { label: 'Reaction', help: 'An emoji. Leave empty to remove the reaction.' }, idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, projectSlug: { label: 'Project Slug', required: true }, diff --git a/n8n/shared/core.js b/n8n/shared/core.js index 6d3f209..67b78c8 100644 --- a/n8n/shared/core.js +++ b/n8n/shared/core.js @@ -58,7 +58,7 @@ function messageBody(action, p) { // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); - } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || p.media.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } const media = { [p.mediaSource]: p.media }; @@ -66,8 +66,9 @@ function messageBody(action, p) { if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); body.content = { [p.mediaType]: media }; } else if (action === 'react') { - if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); - body.id = p.messageId; body.reaction = p.reaction; + const reaction = p.reaction ?? ''; + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = reaction; } else throw new IntegrationError('unknown_action'); return body; } diff --git a/n8n/shared/hooks.js b/n8n/shared/hooks.js index 577f987..cdd4ee3 100644 --- a/n8n/shared/hooks.js +++ b/n8n/shared/hooks.js @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); +const { createHmac, timingSafeEqual } = require('node:crypto'); const { events } = require('./catalog.js'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.js'); function identity(auth, group, subscriptionKey) { @@ -20,6 +20,11 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } +function creationKey(markerKey, registrationKey) { + required(registrationKey, 'registration_key'); + if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); + return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +} function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; @@ -54,18 +59,22 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { url = callback(url); - const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const marker = identity(auth, group, subscriptionKey); + const createKey = creationKey(marker.key, registrationKey); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); - const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - // One create invocation owns its key. A later activation must not replay a deleted hook. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); + // Saved trigger configuration owns this generation across uncertain retries. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - return record(r.data.webhook); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); + return record(live); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -92,6 +101,6 @@ function authenticate(auth, group, subscriptionKey, incomingHeaders) { function envelope(body, group) { if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); // Copy the defined envelope only. Never export arbitrary top-level transport fields. - return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; + return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/pipedream/actions/react/react.mjs b/pipedream/actions/react/react.mjs index 99192d1..efb0d5e 100644 --- a/pipedream/actions/react/react.mjs +++ b/pipedream/actions/react/react.mjs @@ -44,8 +44,8 @@ export default { "reaction": { "type": "string", "label": "Reaction", - "optional": false, - "description": "An emoji, or an empty string to remove the reaction." + "optional": true, + "description": "An emoji. Leave empty to remove the reaction." }, "idempotencyKey": { "type": "string", diff --git a/pipedream/common/catalog.cjs b/pipedream/common/catalog.cjs index e7ed5c9..eb14536 100644 --- a/pipedream/common/catalog.cjs +++ b/pipedream/common/catalog.cjs @@ -26,7 +26,7 @@ const fields = { caption: { label: 'Caption' }, filename: { label: 'Filename', help: 'Used for file messages.' }, messageId: { label: 'Message ID', required: true }, - reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + reaction: { label: 'Reaction', help: 'An emoji. Leave empty to remove the reaction.' }, idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, projectSlug: { label: 'Project Slug', required: true }, diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs index 6d3f209..67b78c8 100644 --- a/pipedream/common/core.cjs +++ b/pipedream/common/core.cjs @@ -58,7 +58,7 @@ function messageBody(action, p) { // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); - } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || p.media.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } const media = { [p.mediaSource]: p.media }; @@ -66,8 +66,9 @@ function messageBody(action, p) { if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); body.content = { [p.mediaType]: media }; } else if (action === 'react') { - if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); - body.id = p.messageId; body.reaction = p.reaction; + const reaction = p.reaction ?? ''; + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = reaction; } else throw new IntegrationError('unknown_action'); return body; } diff --git a/pipedream/common/hooks.cjs b/pipedream/common/hooks.cjs index 322aa0c..ff5cf15 100644 --- a/pipedream/common/hooks.cjs +++ b/pipedream/common/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); +const { createHmac, timingSafeEqual } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -20,6 +20,11 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } +function creationKey(markerKey, registrationKey) { + required(registrationKey, 'registration_key'); + if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); + return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +} function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; @@ -54,18 +59,22 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { url = callback(url); - const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const marker = identity(auth, group, subscriptionKey); + const createKey = creationKey(marker.key, registrationKey); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); - const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - // One create invocation owns its key. A later activation must not replay a deleted hook. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); + // Saved trigger configuration owns this generation across uncertain retries. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - return record(r.data.webhook); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); + return record(live); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -92,6 +101,6 @@ function authenticate(auth, group, subscriptionKey, incomingHeaders) { function envelope(body, group) { if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); // Copy the defined envelope only. Never export arbitrary top-level transport fields. - return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; + return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/pipedream/polymorfa.app.mjs b/pipedream/polymorfa.app.mjs index 802d89e..56d1fd5 100644 --- a/pipedream/polymorfa.app.mjs +++ b/pipedream/polymorfa.app.mjs @@ -16,7 +16,7 @@ export default { }; }, execute($, action, input) { return core.execute(action, input, this.auth(), this.transport($), 'pipedream'); }, - subscribe($, url, group, key) { return hooks.subscribe(this.auth(), url, group, key, this.transport($), 'pipedream'); }, + subscribe($, url, group, key, registrationKey) { return hooks.subscribe(this.auth(), url, group, key, this.transport($), 'pipedream', registrationKey); }, unsubscribe($, owned) { return hooks.unsubscribe(this.auth(), owned.id, owned.url, owned.group, owned.subscriptionKey, this.transport($), 'pipedream', owned.markerName); }, authenticate(group, key, headers) { return hooks.authenticate(this.auth(), group, key, headers); }, envelope(body, group) { return hooks.envelope(body, group); }, diff --git a/pipedream/sources/callEnded/callEnded.mjs b/pipedream/sources/callEnded/callEnded.mjs index b868f06..9d7dca5 100644 --- a/pipedream/sources/callEnded/callEnded.mjs +++ b/pipedream/sources/callEnded/callEnded.mjs @@ -4,10 +4,11 @@ export default { key: "polymorfa-callEnded", name: "Call Ended", description: "Triggers when a call ends.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, + registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "callEnded", this.subscriptionKey); + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "callEnded", this.subscriptionKey, this.registrationKey); this.db.set('ownedSubscription', owned); }, async deactivate() { diff --git a/pipedream/sources/messageReceived/messageReceived.mjs b/pipedream/sources/messageReceived/messageReceived.mjs index 1edf127..fa10ca9 100644 --- a/pipedream/sources/messageReceived/messageReceived.mjs +++ b/pipedream/sources/messageReceived/messageReceived.mjs @@ -4,10 +4,11 @@ export default { key: "polymorfa-messageReceived", name: "New Message", description: "Triggers when a message is received.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, + registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageReceived", this.subscriptionKey); + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageReceived", this.subscriptionKey, this.registrationKey); this.db.set('ownedSubscription', owned); }, async deactivate() { diff --git a/pipedream/sources/messageState/messageState.mjs b/pipedream/sources/messageState/messageState.mjs index b215cb0..03d9842 100644 --- a/pipedream/sources/messageState/messageState.mjs +++ b/pipedream/sources/messageState/messageState.mjs @@ -4,10 +4,11 @@ export default { key: "polymorfa-messageState", name: "Message State Changed", description: "Triggers when a message is sent, acknowledged, failed or updated.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, + registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageState", this.subscriptionKey); + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageState", this.subscriptionKey, this.registrationKey); this.db.set('ownedSubscription', owned); }, async deactivate() { diff --git a/pipedream/sources/numberState/numberState.mjs b/pipedream/sources/numberState/numberState.mjs index 505e5ad..ebbc6fd 100644 --- a/pipedream/sources/numberState/numberState.mjs +++ b/pipedream/sources/numberState/numberState.mjs @@ -4,10 +4,11 @@ export default { key: "polymorfa-numberState", name: "Number State Changed", description: "Triggers when a Number changes state, connects or logs out.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, + registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "numberState", this.subscriptionKey); + const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "numberState", this.subscriptionKey, this.registrationKey); this.db.set('ownedSubscription', owned); }, async deactivate() { diff --git a/scripts/generate.mjs b/scripts/generate.mjs index 93271e3..ef51e0c 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -4,6 +4,7 @@ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const { actions, fields, events } = require('../shared/catalog.cjs'); const core = require('../shared/core.cjs'); +const hooks = require('../shared/hooks.cjs'); const check = process.argv.includes('--check'); let drift = false; function emit(path, value) { @@ -44,7 +45,7 @@ for (const [key, action] of Object.entries(actions)) { } emit(`pipedream/actions/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(action.label)}, description: ${JSON.stringify(action.description)},\n version: '0.1.0', type: 'action',\n props: { polymorfa, ...${JSON.stringify(props, null, 2)} },\n async run({ $ }) {\n const input = Object.fromEntries(${JSON.stringify(action.fields)}.map(key => [key, this[key]]));\n return this.polymorfa.execute($, ${JSON.stringify(key)}, input);\n },\n};\n`); } -for (const [key, event] of Object.entries(events)) emit(`pipedream/sources/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(event.label)}, description: ${JSON.stringify(event.description)},\n version: '0.1.0', type: 'source', dedupe: 'unique',\n props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true },\n subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } },\n hooks: {\n async activate() {\n const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, ${JSON.stringify(key)}, this.subscriptionKey);\n this.db.set('ownedSubscription', owned);\n },\n async deactivate() {\n const owned = this.db.get('ownedSubscription');\n if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); }\n },\n },\n async run(event) {\n if (!this.polymorfa.authenticate(${JSON.stringify(key)}, this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; }\n let data;\n try { data = this.polymorfa.envelope(event.body, ${JSON.stringify(key)}); } catch { this.http.respond({ status: 400 }); return; }\n this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) });\n this.http.respond({ status: 200 });\n },\n};\n`); +for (const [key, event] of Object.entries(events)) emit(`pipedream/sources/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(event.label)}, description: ${JSON.stringify(event.description)},\n version: '0.1.0', type: 'source', dedupe: 'unique',\n props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true },\n subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' },\n registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } },\n hooks: {\n async activate() {\n const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, ${JSON.stringify(key)}, this.subscriptionKey, this.registrationKey);\n this.db.set('ownedSubscription', owned);\n },\n async deactivate() {\n const owned = this.db.get('ownedSubscription');\n if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); }\n },\n },\n async run(event) {\n if (!this.polymorfa.authenticate(${JSON.stringify(key)}, this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; }\n let data;\n try { data = this.polymorfa.envelope(event.body, ${JSON.stringify(key)}); } catch { this.http.respond({ status: 400 }); return; }\n this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) });\n this.http.respond({ status: 200 });\n },\n};\n`); // Make custom IML functions reuse the exact shared request/response behavior. // Every function fits Make's 5,000-character limit. They require provider enablement. @@ -61,7 +62,9 @@ imlFunction('polymorfaRequest', 'action, p, auth', `${errorClass}\nconst ORIGIN imlFunction('polymorfaResult', 'status, headers, body', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body });'); imlFunction('polymorfaSegment', 'value', `${errorClass}\n${core.required}\n${core.segment}`, "return segment(value, 'resource_id');"); imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst derive = label => iml.sha256(label + ':' + scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + derive('marker').slice(0, 24), value: derive('receiver'), key: derive('create') };`); -imlFunction('polymorfaCreateKey', 'key, nonce', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9-]{16,64}$/.test(nonce)) throw new Error('Missing webhook activation identity.');\nreturn 'integration-create-' + key + '-' + nonce;`); +imlFunction('polymorfaCreateKey', 'key, registrationKey', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof registrationKey !== 'string' || !registrationKey.trim() || registrationKey.length > 200) throw new Error('Missing webhook creation key.');\nreturn 'integration-create-' + iml.sha256(registrationKey, 'hex', key);`); +imlFunction('polymorfaEnvelope', 'body, group', `${errorClass}\nconst events = ${JSON.stringify(events)};\n${hooks.envelope}`, 'return envelope(body, group);'); +imlFunction('polymorfaLiveHook', 'body, auth, url, group, key, expectedId', '', `const id = iml.polymorfaFindHook(body, auth, url, group, key);\nif (!id || id !== expectedId) throw new Error('Subscription receipt needs reconciliation.');\nreturn id;`); imlFunction('polymorfaCallback', 'value', '', `if (typeof value !== 'string' || /[\\s\\\\\\x00-\\x1f\\x7f]/.test(value)) throw new Error('Invalid callback.');\nconst parts = value.match(/^https:\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?$/i);\nif (!parts || parts[1].includes('@')) throw new Error('Invalid callback.');\nreturn 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || '');`); imlFunction('polymorfaError', 'status, headers, body', '', `try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; }`); imlFunction('polymorfaReceive', 'expectedName, expectedValue, group, headers, body', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase());\nif (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false;\nreturn !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload));`); @@ -99,20 +102,24 @@ for (const [key, e] of Object.entries(events)) { return result; }; emit(`make/modules/${key}/interface.json`, schemas.reduce((all, schema) => merge(all, interfaceFields(schema, specs.messaging)), [])); - emit(`make/webhooks/${key}/parameters.json`, [{ name: 'subscriptionKey', label: 'Subscription Key', type: 'text', required: true, help: 'Unique stable label for this webhook. Events are project-wide.' }]); + emit(`make/webhooks/${key}/parameters.json`, [ + { name: 'subscriptionKey', label: 'Subscription Key', type: 'text', required: true, help: 'Unique stable label for this webhook. Events are project-wide.' }, + { name: 'registrationKey', label: 'Webhook Creation Key', type: 'text', required: true, help: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, + ]); emit(`make/webhooks/${key}/metadata.json`, { type: 'dedicated', connection: 'project', label: e.label }); const marker = `polymorfaHook(connection; '${key}'; parameters.subscriptionKey)`; const markerField = field => `{{get(${marker}; '${field}')}}`; emit(`make/webhooks/${key}/attach.json`, [ - { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}`, activationId: '{{uuid}}' }, data: { externalHookId: '{{temp.ownedId}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, - { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); temp.activationId)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { data: { externalHookId: '{{body.data.webhook.id}}', ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}` } } }, + { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); parameters.registrationKey)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { temp: { createdId: '{{body.data.webhook.id}}' } } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { data: { externalHookId: `{{polymorfaLiveHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}`, ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, ]); emit(`make/webhooks/${key}/detach.json`, [ { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedPresent: `{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}')}}` } } }, { condition: '{{temp.ownedPresent}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'DELETE', headers: { 'Idempotency-Key': 'integration-delete-{{webhook.externalHookId}}' } }, ]); const valid = `polymorfaReceive(parameters.receiverName; parameters.receiverValue; '${key}'; headers; body)`; - emit(`make/webhooks/${key}/communication.json`, { condition: `{{${valid}}}`, respond: { status: `{{if(${valid}; 200; 401)}}`, type: 'json', body: {} }, output: { id: '{{body.id}}', session: '{{body.session}}', timestamp: '{{body.timestamp}}', event: '{{body.event}}', payload: '{{body.payload}}' } }); + emit(`make/webhooks/${key}/communication.json`, { condition: `{{${valid}}}`, respond: { status: `{{if(${valid}; 200; 401)}}`, type: 'json', body: {} }, output: `{{polymorfaEnvelope(body; '${key}')}}` }); } if (drift) process.exitCode = 1; else console.log(check ? 'Generated adapter files are current.' : 'Generated native adapter files.'); diff --git a/shared/catalog.cjs b/shared/catalog.cjs index 408d9c4..378d8e7 100644 --- a/shared/catalog.cjs +++ b/shared/catalog.cjs @@ -25,7 +25,7 @@ const fields = { caption: { label: 'Caption' }, filename: { label: 'Filename', help: 'Used for file messages.' }, messageId: { label: 'Message ID', required: true }, - reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + reaction: { label: 'Reaction', help: 'An emoji. Leave empty to remove the reaction.' }, idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, projectSlug: { label: 'Project Slug', required: true }, diff --git a/shared/core.cjs b/shared/core.cjs index 078feee..a71de62 100644 --- a/shared/core.cjs +++ b/shared/core.cjs @@ -57,7 +57,7 @@ function messageBody(action, p) { // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); - } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || p.media.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } const media = { [p.mediaSource]: p.media }; @@ -65,8 +65,9 @@ function messageBody(action, p) { if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); body.content = { [p.mediaType]: media }; } else if (action === 'react') { - if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); - body.id = p.messageId; body.reaction = p.reaction; + const reaction = p.reaction ?? ''; + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = reaction; } else throw new IntegrationError('unknown_action'); return body; } diff --git a/shared/hooks.cjs b/shared/hooks.cjs index 670056c..c7a9ff8 100644 --- a/shared/hooks.cjs +++ b/shared/hooks.cjs @@ -1,6 +1,6 @@ 'use strict'; -const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); +const { createHmac, timingSafeEqual } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -19,6 +19,11 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } +function creationKey(markerKey, registrationKey) { + required(registrationKey, 'registration_key'); + if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); + return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +} function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; @@ -53,18 +58,22 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { url = callback(url); - const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const marker = identity(auth, group, subscriptionKey); + const createKey = creationKey(marker.key, registrationKey); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); - const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - // One create invocation owns its key. A later activation must not replay a deleted hook. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); + // Saved trigger configuration owns this generation across uncertain retries. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - return record(r.data.webhook); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); + return record(live); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -91,6 +100,6 @@ function authenticate(auth, group, subscriptionKey, incomingHeaders) { function envelope(body, group) { if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); // Copy the defined envelope only. Never export arbitrary top-level transport fields. - return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; + return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; diff --git a/tests/core.test.cjs b/tests/core.test.cjs index 628498d..b84fbd5 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -12,6 +12,22 @@ const now = Date.parse('2026-11-01T12:00:00Z'); const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; const input = { session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'An order is ready.', idempotencyKey: 'order-123-notification-1', writeCreatedAt: '2026-11-01T11:59:00Z' }; +test('valid base64 media through the decoded limit does not exhaust the regex stack', () => { + for (const size of [core.MAX_MEDIA_BYTES - 2, core.MAX_MEDIA_BYTES - 1, core.MAX_MEDIA_BYTES]) { + const media = Buffer.alloc(size).toString('base64'); + const request = core.buildRequest('sendMedia', { ...input, mediaType: 'file', mediaSource: 'base64', media }, auth, 'test', now); + assert.equal(request.body.content.file.base64, media); + } + for (const media of ['A===', 'AA=A', 'AAA', 'A A=', Buffer.alloc(core.MAX_MEDIA_BYTES + 1).toString('base64')]) { + assert.throws(() => core.buildRequest('sendMedia', { ...input, mediaType: 'file', mediaSource: 'base64', media }, auth, 'test', now), e => e.code === 'invalid_or_oversized_media'); + } +}); + +test('native empty or omitted reactions remove a reaction while invalid values are refused', () => { + for (const reaction of ['', undefined, null]) assert.equal(core.buildRequest('react', { ...input, messageId: '9007199254740993124', reaction }, auth, 'test', now).body.reaction, ''); + assert.throws(() => core.buildRequest('react', { ...input, messageId: '9007199254740993124', reaction: 1 }, auth, 'test', now), e => e.code === 'invalid_reaction'); +}); + test('non-JSON gateway failures retain uncertain-write recovery and safe retry metadata', () => { assert.throws(() => core.parseResponse({ status: 502, headers: { 'retry-after': '8' }, body: 'upstream unavailable' }), e => e.code === 'outcome_unknown' && e.retryAfter === 8 && /Reconcile/.test(e.message)); assert.throws(() => core.parseResponse({ status: 200, body: 'not JSON' }), e => e.code === 'invalid_response'); diff --git a/tests/hooks.test.cjs b/tests/hooks.test.cjs index 49d576b..ab2626a 100644 --- a/tests/hooks.test.cjs +++ b/tests/hooks.test.cjs @@ -8,16 +8,20 @@ const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '1 const url = 'https://example.com/synthetic-callback'; const key = 'synthetic-trigger-1'; function server() { - const rows = []; const calls = []; let lost = false; + const rows = []; const calls = []; const receipts = new Map(); let lost = false; return { rows, calls, loseNextCreate() { lost = true; }, async request(r) { calls.push(r); assert.equal(new URL(r.url).origin, 'https://api.polymorfa.com'); assert.equal(r.headers.Authorization, `Bearer ${auth.apiKey}`); if (r.method === 'GET' && r.url.endsWith('/webhooks')) return { status: 200, body: { data: rows, page: { hasMore: false, nextCursor: null } } }; if (r.method === 'POST') { - const row = { ...r.body, owner: 'project', projectId: auth.projectId, id: '22222222-2222-4222-8222-222222222222', headers: r.body.headers.map(h => ({ name: h.name })) }; rows.push(row); + const requestKey = r.headers['Idempotency-Key']; + if (receipts.has(requestKey)) return receipts.get(requestKey); + const row = { ...r.body, owner: 'project', projectId: auth.projectId, id: `synthetic-hook-${receipts.size + 1}`, headers: r.body.headers.map(h => ({ name: h.name })) }; rows.push(row); + const receipt = { status: 200, body: { data: { webhook: row, secret: 'SYNTHETIC-ONE-TIME-SIGNING-SECRET' } } }; + receipts.set(requestKey, receipt); if (lost) { lost = false; throw new Error('Response lost after accepted create'); } - return { status: 200, body: { data: { webhook: row, secret: 'SYNTHETIC-ONE-TIME-SIGNING-SECRET' } } }; + return receipt; } const row = rows.find(row => r.url.endsWith('/' + row.id)); if (!row) return { status: 404, body: { error: { code: 'not_found' } } }; @@ -27,10 +31,10 @@ function server() { } for (const group of Object.keys(events)) test(`${group}: activation, reactivation, authentication and owned cleanup`, async () => { const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, url, group, key, transport, 'test'); + const first = await hooks.subscribe(auth, url, group, key, transport, 'test', 'registration-generation-1'); assert.deepEqual(Object.keys(first).sort(), ['group', 'id', 'markerName', 'subscriptionKey', 'url']); assert.ok(!JSON.stringify(first).includes('SYNTHETIC-ONE-TIME-SIGNING-SECRET')); - assert.deepEqual(await hooks.subscribe(auth, url, group, key, transport, 'test'), first); + assert.deepEqual(await hooks.subscribe(auth, url, group, key, transport, 'test', 'registration-generation-1'), first); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); const marker = hooks.identity(auth, group, key); assert.ok(hooks.authenticate(auth, group, key, { [marker.name.toLowerCase()]: marker.value })); @@ -42,19 +46,19 @@ for (const group of Object.keys(events)) test(`${group}: activation, reactivatio }); test('an ambiguous accepted create is recovered without another create', async () => { const s = server(); s.loseNextCreate(); - const result = await hooks.subscribe(auth, url, 'messageReceived', key, s.request.bind(s), 'test'); + const result = await hooks.subscribe(auth, url, 'messageReceived', key, s.request.bind(s), 'test', 'registration-generation-1'); assert.ok(result.id); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('an operator-disabled hook is preserved instead of silently enabling or duplicating it', async () => { const s = server(); const t = s.request.bind(s); - await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); s.rows[0].enabled = false; - await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'), e => e.code === 'subscription_changed_requires_reconciliation'); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); s.rows[0].enabled = false; + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'), e => e.code === 'subscription_changed_requires_reconciliation'); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('a different callback cannot reuse the same active subscription identity', async () => { const s = server(); const t = s.request.bind(s); - await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); - await assert.rejects(hooks.subscribe(auth, 'https://other.example/hook', 'messageReceived', key, t, 'test'), e => e.code === 'subscription_key_already_in_use'); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); + await assert.rejects(hooks.subscribe(auth, 'https://other.example/hook', 'messageReceived', key, t, 'test', 'registration-generation-1'), e => e.code === 'subscription_key_already_in_use'); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('owned lookup follows cursor pages and refuses looping cursors', async () => { @@ -67,7 +71,7 @@ test('owned lookup follows cursor pages and refuses looping cursors', async () = await assert.rejects(hooks.find(auth, url, 'messageReceived', key, async () => ({ status: 200, body: { data: [], page: { hasMore: true, nextCursor: 'loop' } } }), 'test'), e => e.code === 'invalid_subscription_pagination'); }); test('cleanup refuses another subscription and never falls back to broader credentials', async () => { - const s = server(); const t = s.request.bind(s); const owned = await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test'); + const s = server(); const t = s.request.bind(s); const owned = await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); await assert.rejects(hooks.unsubscribe(auth, owned.id, 'https://another.example/hook', 'messageReceived', key, t, 'test'), e => e.code === 'subscription_ownership_mismatch'); assert.equal(s.calls.filter(r => r.method === 'DELETE').length, 0); }); @@ -96,22 +100,44 @@ test('public marker and create keys do not reveal receiver verifier material', ( assert.ok(!marker.name.includes(marker.value.slice(0, 24))); }); -test('reactivating a deleted hook uses a new create identity', async () => { +test('saved creation generations prevent deleted-receipt activation and permit confirmed recreation', async () => { const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'); + const first = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-1'); await hooks.unsubscribe(auth, first.id, url, 'messageReceived', key, transport, 'test'); - await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'); + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-1'), e => e.code === 'subscription_requires_reconciliation'); + assert.equal(s.rows.length, 0); + const second = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-2'); const requests = s.calls.filter(r => r.method === 'POST'); - assert.equal(requests.length, 2); - assert.notEqual(requests[0].headers['Idempotency-Key'], requests[1].headers['Idempotency-Key']); + assert.equal(requests.length, 3); assert.equal(s.rows.length, 1); assert.notEqual(second.id, first.id); + assert.equal(requests[0].headers['Idempotency-Key'], requests[1].headers['Idempotency-Key']); + assert.notEqual(requests[0].headers['Idempotency-Key'], requests[2].headers['Idempotency-Key']); assert.ok(requests.every(r => !r.headers['Idempotency-Key'].includes(hooks.identity(auth, 'messageReceived', key).value))); }); +test('uncertain creation retries use the same saved key and refuse a missing generation', async () => { + const keys = []; + const transport = async r => { + if (r.method === 'GET') return { status: 200, body: { data: [], page: { hasMore: false } } }; + keys.push(r.headers['Idempotency-Key']); throw new Error('Uncertain synthetic transport'); + }; + for (let i = 0; i < 2; i++) await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'saved-generation'), e => e.code === 'subscription_requires_reconciliation'); + assert.equal(keys.length, 2); assert.equal(keys[0], keys[1]); + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'), e => e.code === 'invalid_registration_key'); + assert.equal(keys.length, 2); +}); + +test('public QuickLink reference survives the event projection without arbitrary transport data', () => { + const sample = require('../fixtures/events.json').numberState; + const projected = hooks.envelope({ ...sample, externalId: 'synthetic-order-reference', transportSecret: 'must-not-escape' }, 'numberState'); + assert.equal(projected.externalId, 'synthetic-order-reference'); assert.ok(!('transportSecret' in projected)); + assert.ok(!('externalId' in hooks.envelope({ ...sample, externalId: { invalid: true } }, 'numberState'))); +}); + test('callback normalization preserves ownership across activation and cleanup', async () => { const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test'); + const first = await hooks.subscribe(auth, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test', 'registration-generation-1'); assert.equal(s.rows[0].url, 'https://example.com/'); - assert.deepEqual(await hooks.subscribe(auth, 'https://example.com/', 'messageReceived', key, transport, 'test'), first); + assert.deepEqual(await hooks.subscribe(auth, 'https://example.com/', 'messageReceived', key, transport, 'test', 'registration-generation-1'), first); await hooks.unsubscribe(auth, first.id, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test'); assert.equal(s.rows.length, 0); assert.equal(hooks.sameCallback(url + '?key=A', url + '?key=a'), false); @@ -122,6 +148,6 @@ for (const status of [403, 422, 429]) test(`definite subscription rejection ${st await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, async r => { if (r.method === 'GET') { reads++; return { status: 200, body: { data: [], page: { hasMore: false } } }; } return { status, headers: { 'retry-after': '17', 'x-request-id': 'safe-request' }, body: { error: { code: 'scope_denied' } } }; - }, 'test'), e => e.status === status && e.code === 'scope_denied' && e.retryAfter === 17 && e.requestId === 'safe-request'); + }, 'test', 'registration-generation-1'), e => e.status === status && e.code === 'scope_denied' && e.retryAfter === 17 && e.requestId === 'safe-request'); assert.equal(reads, 1); }); diff --git a/tests/make.test.cjs b/tests/make.test.cjs index 77d1224..cf6638d 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -45,11 +45,28 @@ test('Make activation keys are independent of receiver secrets and change betwee const first = iml.polymorfaCreateKey(marker.key, '11111111-1111-4111-8111-111111111111'); const second = iml.polymorfaCreateKey(marker.key, '22222222-2222-4222-8222-222222222222'); assert.notEqual(first, second); assert.ok(!first.includes(marker.value)); + assert.equal(first, hooks.creationKey(marker.key, '11111111-1111-4111-8111-111111111111')); + assert.equal(first, iml.polymorfaCreateKey(marker.key, '11111111-1111-4111-8111-111111111111')); assert.throws(() => iml.polymorfaCreateKey(marker.key, '')); const base = JSON.parse(readFileSync('make/base.json')); for (const path of ['request.headers.idempotency-key', 'request.body.headers', 'response.body.data.idempotency.key']) assert.ok(base.log.sanitize.includes(path)); }); +test('Make verifies live creation receipts and maps the optional public QuickLink reference', () => { + const marker = iml.polymorfaHook(auth, 'numberState', 'key'); + const row = { id: 'owned', projectId: auth.projectId, owner: 'project', url: 'https://example.com/', enabled: true, format: 'native', headers: [{ name: marker.name }], eventTypes: ['session.status', 'session.connected', 'session.logged_out'] }; + const args = [auth, row.url, 'numberState', 'key', 'owned']; + assert.equal(iml.polymorfaLiveHook({ data: [row], page: { hasMore: false } }, ...args), 'owned'); + assert.throws(() => iml.polymorfaLiveHook({ data: [], page: { hasMore: false } }, ...args)); + assert.throws(() => iml.polymorfaLiveHook({ data: [{ ...row, id: 'different' }], page: { hasMore: false } }, ...args)); + const sample = { ...require('../fixtures/events.json').numberState, externalId: 'synthetic-order-reference', transportSecret: 'must-not-escape' }; + assert.deepEqual(JSON.parse(JSON.stringify(iml.polymorfaEnvelope(sample, 'numberState'))), hooks.envelope(sample, 'numberState')); + const attach = JSON.parse(readFileSync('make/webhooks/numberState/attach.json')); + assert.ok(attach[1].headers['Idempotency-Key'].includes('parameters.registrationKey')); + assert.ok(attach[2].response.data.externalHookId.includes('polymorfaLiveHook')); + assert.equal(JSON.parse(readFileSync('make/modules/react/parameters.json')).find(p => p.name === 'reaction').required, false); +}); + test('Make detach only deletes owned IDs in a complete successful inventory', () => { const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); const row = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://EXAMPLE.COM:443', format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; diff --git a/tests/native.test.cjs b/tests/native.test.cjs index 23b54b1..cf687c9 100644 --- a/tests/native.test.cjs +++ b/tests/native.test.cjs @@ -72,3 +72,22 @@ test('Zapier consumes native Headers and requires write identity in its editor', } assert.equal(zapier.creates.apiRequest.operation.inputFields.find(f => f.key === 'idempotencyKey').required, false); }); + +test('Zapier routes real throttling middleware through shared parsing and its native retry error', async () => { + const middleware = require('zapier-platform-core/src/http-middlewares/after/throw-for-throttling'); + const errors = require('zapier-platform-core/src/errors'); + const response = request => ({ request, status: 429, headers: new Headers({ 'Retry-After': '12' }), content: '{"error":{"code":"rate_limited"}}' }); + assert.throws(() => middleware(response({})), e => e.name === 'ThrottledError'); + const z = { errors, request: async request => middleware(response(request)) }; + for (const [action, inputData] of [['sendText', input], ['listNumbers', {}]]) { + await assert.rejects(zapier.creates[action].operation.perform(z, { authData: auth, inputData }), e => e.name === 'ThrottledError' && JSON.parse(e.message).delay === 12 && JSON.parse(e.message).message.includes('rate_limited')); + } +}); + +test('Zapier editor permits removal and sends an explicit empty reaction when the field is omitted', async () => { + assert.equal(zapier.creates.react.operation.inputFields.find(f => f.key === 'reaction').required, false); + let body; + await zapier.creates.react.operation.perform({ request: async r => { body = r.body; return { status: 200, content: '{"data":{}}', headers: {} }; } }, { authData: auth, inputData: { ...input, messageId: '739182640518205' } }); + assert.equal(body.reaction, ''); + for (const trigger of Object.values(zapier.triggers)) assert.equal(trigger.operation.inputFields.find(f => f.key === 'registrationKey').required, true); +}); diff --git a/zapier/index.js b/zapier/index.js index abbf00c..ee04b51 100644 --- a/zapier/index.js +++ b/zapier/index.js @@ -6,11 +6,18 @@ const hooks = require('./shared/hooks.cjs'); function transport(z) { return async r => { - const response = await z.request({ method: r.method, url: r.url, headers: r.headers, params: r.query, body: r.body, redirect: 'manual', skipThrowForStatus: true }); + const response = await z.request({ method: r.method, url: r.url, headers: r.headers, params: r.query, body: r.body, redirect: 'manual', skipThrowForStatus: true, throwForThrottlingEarly: false }); const headers = typeof response.headers?.entries === 'function' ? Object.fromEntries(response.headers.entries()) : response.headers; return { status: response.status, headers, body: response.content }; }; } +async function withThrottling(z, task) { + try { return await task(); } + catch (error) { + if (error.status === 429 && z.errors?.ThrottledError) throw new z.errors.ThrottledError(error.message, error.retryAfter); + throw error; + } +} function field(name) { const f = fields[name]; return { key: name, label: f.label, type: 'string', required: !!f.required, helpText: f.help, ...(f.options ? { choices: f.options } : {}), ...(f.default ? { default: f.default } : {}) }; @@ -19,7 +26,7 @@ const creates = Object.fromEntries(Object.entries(actions).map(([key, action]) = key, noun: 'Result', display: { label: action.label, description: action.description }, operation: { inputFields: action.fields.map(name => ({ ...field(name), ...(key !== 'apiRequest' && ['idempotencyKey', 'writeCreatedAt'].includes(name) ? { required: true } : {}) })), - async perform(z, bundle) { return core.execute(key, bundle.inputData, bundle.authData, transport(z), 'zapier'); }, + async perform(z, bundle) { return withThrottling(z, () => core.execute(key, bundle.inputData, bundle.authData, transport(z), 'zapier')); }, sample: { data: { id: '739182640518203' } }, }, }])); @@ -27,17 +34,20 @@ const triggers = Object.fromEntries(Object.entries(events).map(([key, event]) => key, noun: 'Event', display: { label: event.label, description: event.description }, operation: { type: 'hook', canPaginate: false, - inputFields: [{ key: 'subscriptionKey', label: 'Subscription Key', type: 'string', required: true, helpText: 'A unique stable key for this Zap trigger. Project-wide events are sent to Zapier. Use a different key for another trigger.' }], + inputFields: [ + { key: 'subscriptionKey', label: 'Subscription Key', type: 'string', required: true, helpText: 'A unique stable key for this Zap trigger. Project-wide events are sent to Zapier. Use a different key for another trigger.' }, + { key: 'registrationKey', label: 'Webhook Creation Key', type: 'string', required: true, helpText: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, + ], async performSubscribe(z, bundle) { // Zapier's logger scrubs authData values before its HTTP logs run. // Register the derived receiver secret before the registration request. bundle.authData.webhookVerificationSecret = hooks.identity(bundle.authData, key, bundle.inputData.subscriptionKey).value; - const owned = await hooks.subscribe(bundle.authData, bundle.targetUrl, key, bundle.inputData.subscriptionKey, transport(z), 'zapier'); + const owned = await withThrottling(z, () => hooks.subscribe(bundle.authData, bundle.targetUrl, key, bundle.inputData.subscriptionKey, transport(z), 'zapier', bundle.inputData.registrationKey)); return { ...owned, url: bundle.targetUrl }; }, async performUnsubscribe(z, bundle) { const owned = bundle.subscribeData; - await hooks.unsubscribe(bundle.authData, owned.id, owned.url, owned.group, owned.subscriptionKey, transport(z), 'zapier', owned.markerName); return {}; + await withThrottling(z, () => hooks.unsubscribe(bundle.authData, owned.id, owned.url, owned.group, owned.subscriptionKey, transport(z), 'zapier', owned.markerName)); return {}; }, async perform(z, bundle) { if (!hooks.authenticate(bundle.authData, key, bundle.inputData.subscriptionKey, bundle.cleanedRequest.headers)) throw new z.errors.Error('Webhook authentication failed.', 'WebhookAuthentication', 401); @@ -45,7 +55,7 @@ const triggers = Object.fromEntries(Object.entries(events).map(([key, event]) => }, async performList() { return [require('./shared/samples.json')[key]]; }, sample: require('./shared/samples.json')[key], - outputFields: [{ key: 'id', type: 'string' }, { key: 'session', type: 'string' }, { key: 'timestamp', type: 'datetime' }, { key: 'event', type: 'string' }], + outputFields: [{ key: 'id', type: 'string' }, { key: 'session', type: 'string' }, { key: 'externalId', type: 'string' }, { key: 'timestamp', type: 'datetime' }, { key: 'event', type: 'string' }], }, }])); module.exports = { @@ -56,7 +66,7 @@ module.exports = { { key: 'apiKey', label: 'Project Token', type: 'password', required: true, helpText: 'A restricted project token. Connection testing requires sessions:read.' }, { key: 'projectId', label: 'Project ID', type: 'string', required: true }, ], - async test(z, bundle) { await core.execute('listNumbers', {}, bundle.authData, transport(z), 'zapier'); return { projectId: bundle.authData.projectId }; }, + async test(z, bundle) { await withThrottling(z, () => core.execute('listNumbers', {}, bundle.authData, transport(z), 'zapier')); return { projectId: bundle.authData.projectId }; }, }, creates, triggers, }; diff --git a/zapier/shared/catalog.cjs b/zapier/shared/catalog.cjs index e7ed5c9..eb14536 100644 --- a/zapier/shared/catalog.cjs +++ b/zapier/shared/catalog.cjs @@ -26,7 +26,7 @@ const fields = { caption: { label: 'Caption' }, filename: { label: 'Filename', help: 'Used for file messages.' }, messageId: { label: 'Message ID', required: true }, - reaction: { label: 'Reaction', required: true, help: 'An emoji, or an empty string to remove the reaction.' }, + reaction: { label: 'Reaction', help: 'An emoji. Leave empty to remove the reaction.' }, idempotencyKey: { label: 'Idempotency Key', help: 'Required for writes. Map a stable business key unique to this write. Preserve it across retries.' }, writeCreatedAt: { label: 'Write Started At', help: 'Required for writes. ISO timestamp saved when this logical write first began. Preserve it across retries; writes older than 23 hours require reconciliation.' }, projectSlug: { label: 'Project Slug', required: true }, diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs index 6d3f209..67b78c8 100644 --- a/zapier/shared/core.cjs +++ b/zapier/shared/core.cjs @@ -58,7 +58,7 @@ function messageBody(action, p) { // Enforce HTTPS/no embedded credentials here; the API validates/fetches the URI. const address = p.media.match(/^https:\/\/([^/?#]+)(?:[/?#]|$)/i); if (!address || /[\s\\\x00-\x1f\x7f]/.test(p.media) || address[1].includes('@')) throw new IntegrationError('invalid_media_url'); - } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { + } else if (p.media.length > Math.ceil(MAX_MEDIA_BYTES / 3) * 4 || p.media.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(p.media) || Buffer.from(p.media, 'base64').length > MAX_MEDIA_BYTES) { throw new IntegrationError('invalid_or_oversized_media'); } const media = { [p.mediaSource]: p.media }; @@ -66,8 +66,9 @@ function messageBody(action, p) { if (p.mediaType === 'file' && p.filename) media.filename = required(p.filename, 'filename'); body.content = { [p.mediaType]: media }; } else if (action === 'react') { - if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof p.reaction !== 'string') throw new IntegrationError('invalid_reaction'); - body.id = p.messageId; body.reaction = p.reaction; + const reaction = p.reaction ?? ''; + if (!/^[1-9]\d{0,18}$/.test(required(p.messageId, 'message_id')) || typeof reaction !== 'string') throw new IntegrationError('invalid_reaction'); + body.id = p.messageId; body.reaction = reaction; } else throw new IntegrationError('unknown_action'); return body; } diff --git a/zapier/shared/hooks.cjs b/zapier/shared/hooks.cjs index 322aa0c..ff5cf15 100644 --- a/zapier/shared/hooks.cjs +++ b/zapier/shared/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); +const { createHmac, timingSafeEqual } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -20,6 +20,11 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } +function creationKey(markerKey, registrationKey) { + required(registrationKey, 'registration_key'); + if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); + return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +} function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); const types = events[group].types; @@ -54,18 +59,22 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { url = callback(url); - const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: identity(auth, group, subscriptionKey).name }); + const marker = identity(auth, group, subscriptionKey); + const createKey = creationKey(marker.key, registrationKey); + const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); - const marker = identity(auth, group, subscriptionKey); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. try { - // One create invocation owns its key. A later activation must not replay a deleted hook. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, `integration-create-${marker.key}-${randomUUID()}`); + // Saved trigger configuration owns this generation across uncertain retries. + const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - return record(r.data.webhook); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); + return record(live); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -92,6 +101,6 @@ function authenticate(auth, group, subscriptionKey, incomingHeaders) { function envelope(body, group) { if (!events[group] || !body || typeof body !== 'object' || Array.isArray(body) || typeof body.id !== 'string' || !body.id || typeof body.session !== 'string' || !body.session || typeof body.timestamp !== 'string' || !Number.isFinite(Date.parse(body.timestamp)) || !events[group].types.includes(body.event) || !body.payload || typeof body.payload !== 'object' || Array.isArray(body.payload)) throw new IntegrationError('invalid_event'); // Copy the defined envelope only. Never export arbitrary top-level transport fields. - return { id: body.id, session: body.session, timestamp: body.timestamp, event: body.event, payload: body.payload }; + return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; From 5ede70016fc5262305b0d6774b7d1948c57351f8 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:08:17 +0200 Subject: [PATCH 07/14] Create SDK drift test fixtures under the system temp directory --- tests/sdk-drift.test.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/sdk-drift.test.mjs b/tests/sdk-drift.test.mjs index 6730d67..aa54792 100644 --- a/tests/sdk-drift.test.mjs +++ b/tests/sdk-drift.test.mjs @@ -2,15 +2,13 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'; -import { homedir } from 'node:os'; +import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawnSync } from 'node:child_process'; test('SDK drift detects a changed nested component referenced only by the SDK', t => { - const parent = join(homedir(), '.polymorfa-agent-work', 'integration-sdk-drift-tests'); - mkdirSync(parent, { recursive: true }); - const root = mkdtempSync(join(parent, 'fixture-')); + const root = mkdtempSync(join(tmpdir(), 'integration-sdk-drift-')); t.after(() => rmSync(root, { recursive: true, force: true })); mkdirSync(join(root, 'contracts')); const source = 'a'.repeat(40); From b2b91a26bac4a3b1ca6f29571a5c2885f1382a35 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:37:11 +0200 Subject: [PATCH 08/14] Gate n8n publication on the whole reviewed tree before repository code runs The release gate now refuses any change after review except release receipts, so tests, .npmrc and .nvmrc are covered. Checks move to a verify job without an OIDC token that runs the gate first. The publish job repeats the check with git and jq before Node setup or any repository script. --- .github/workflows/publish-n8n.yml | 51 ++++++++++++++++++++++++++++--- MAINTAINING.md | 7 +++-- scripts/release-gate.mjs | 7 +++-- scripts/release-policy.mjs | 3 ++ tests/release-policy.test.mjs | 37 ++++++++++++++++++++++ 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish-n8n.yml b/.github/workflows/publish-n8n.yml index 8211457..75a893e 100644 --- a/.github/workflows/publish-n8n.yml +++ b/.github/workflows/publish-n8n.yml @@ -13,32 +13,73 @@ on: permissions: contents: read jobs: - publish: + verify: + # Runs repository code, so it never receives an OIDC token. if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - environment: npm-release permissions: contents: read - id-token: write steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 ref: ${{ github.sha }} persist-credentials: false + # Gate first: nothing else from the repository runs before the source binding is checked. + - run: node scripts/release-gate.mjs + env: + RELEASE_VERSION: ${{ inputs.version }} + RELEASE_REVIEWED_SOURCE: ${{ inputs.reviewed-source }} + RELEASE_REF: ${{ github.ref }} + RELEASE_SOURCE: ${{ github.sha }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version-file: .nvmrc - registry-url: https://registry.npmjs.org - run: npm install --global npm@11.20.0 - run: npm ci --ignore-scripts --no-audit - run: npm run check - run: npm run lint --workspace n8n-nodes-polymorfa - run: npm run check:sdk - - run: node scripts/release-gate.mjs + publish: + needs: verify + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + environment: npm-release + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + ref: ${{ github.sha }} + persist-credentials: false + # Git and jq only. This runs before any repository file is executed or read as configuration + # (.nvmrc, .npmrc, package scripts), because this job can mint an npm OIDC token. + - name: Check reviewed source binding + shell: bash env: RELEASE_VERSION: ${{ inputs.version }} RELEASE_REVIEWED_SOURCE: ${{ inputs.reviewed-source }} RELEASE_REF: ${{ github.ref }} RELEASE_SOURCE: ${{ github.sha }} + run: | + set -euo pipefail + [[ "$RELEASE_REF" == refs/heads/main ]] + [[ "$RELEASE_SOURCE" =~ ^[a-f0-9]{40}$ && "$RELEASE_REVIEWED_SOURCE" =~ ^[a-f0-9]{40}$ ]] + [[ "$(git rev-parse HEAD)" == "$RELEASE_SOURCE" ]] + [[ "$RELEASE_VERSION" =~ ^[0-9A-Za-z.+-]+$ ]] + [[ "$(git show HEAD:n8n/package.json | jq -r .version)" == "$RELEASE_VERSION" ]] + receipt="$(git show "HEAD:releases/$RELEASE_VERSION.json" | jq -r .reviewedSource)" + [[ "$receipt" == "$RELEASE_REVIEWED_SOURCE" ]] + git merge-base --is-ancestor "$RELEASE_REVIEWED_SOURCE" HEAD + # Everything except release receipts must match the reviewed source. + git diff --exit-code "$RELEASE_REVIEWED_SOURCE" HEAD -- . ':(exclude)releases' + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + registry-url: https://registry.npmjs.org + - run: npm install --global npm@11.20.0 + - run: npm ci --ignore-scripts --no-audit + - run: npm run build - run: npm publish --workspace n8n-nodes-polymorfa --provenance --access public diff --git a/MAINTAINING.md b/MAINTAINING.md index bbcaa13..9dae562 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -49,8 +49,11 @@ trusted publisher has been configured by adding this workflow. Review is still required; the gate is bookkeeping and does not authenticate human authorization. Publication accepts only a `main` dispatch, checks out its immutable commit, and -requires the dispatch's reviewed-source commit to match the release receipt and -its executable files. Restrict the `npm-release` environment to protected `main` +requires the dispatch's reviewed-source commit to match the release receipt. Every +path except `releases/` must match the reviewed source, including tests, `.npmrc` +and `.nvmrc`. The `verify` job runs the gate before any install or test and has no +OIDC token. The `publish` job, which has `id-token: write`, repeats the check with +git and jq before Node setup, `.npmrc` use or any repository script. Restrict the `npm-release` environment to protected `main` and require an independent release reviewer before enabling the trusted publisher. These external settings cannot be secured by editable workflow code alone. diff --git a/scripts/release-gate.mjs b/scripts/release-gate.mjs index 1fccb98..b4c2157 100644 --- a/scripts/release-gate.mjs +++ b/scripts/release-gate.mjs @@ -1,7 +1,7 @@ import { readFileSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; import assert from 'node:assert/strict'; -import { validateReleaseIdentity } from './release-policy.mjs'; +import { validateReleaseIdentity, RELEASE_GATE_PATHSPEC } from './release-policy.mjs'; const pkg = JSON.parse(readFileSync('n8n/package.json')); assert.equal(process.env.RELEASE_VERSION, pkg.version, 'Dispatch version must match the reviewed package.'); const receipt = JSON.parse(readFileSync(`releases/${pkg.version}.json`)); @@ -11,6 +11,7 @@ validateReleaseIdentity({ ref: process.env.RELEASE_REF, source: process.env.RELE checkedOut: execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(), reviewed: process.env.RELEASE_REVIEWED_SOURCE, receiptReviewed: receipt.reviewedSource }); execFileSync('git', ['merge-base', '--is-ancestor', receipt.reviewedSource, process.env.RELEASE_SOURCE]); -// Receipt-only changes may follow the reviewed candidate; executable source must match. -execFileSync('git', ['diff', '--exit-code', receipt.reviewedSource, 'HEAD', '--', 'shared', 'n8n', 'contracts', 'scripts', 'fixtures', 'package.json', 'package-lock.json', '.github/workflows/publish-n8n.yml'], { stdio: 'inherit' }); +// Only release receipts may change after the reviewed candidate. Every other path, including +// tests, .npmrc, .nvmrc and workflows, must match it. +execFileSync('git', ['diff', '--exit-code', receipt.reviewedSource, 'HEAD', '--', ...RELEASE_GATE_PATHSPEC], { stdio: 'inherit' }); console.log('Required publication receipts are recorded. Protected environment approval and npm trusted publisher configuration remain required.'); diff --git a/scripts/release-policy.mjs b/scripts/release-policy.mjs index ad40027..41ab97c 100644 --- a/scripts/release-policy.mjs +++ b/scripts/release-policy.mjs @@ -1,5 +1,8 @@ import assert from 'node:assert/strict'; +// The reviewed source must match the published commit everywhere except release receipts. +export const RELEASE_GATE_PATHSPEC = Object.freeze(['.', ':(exclude)releases']); + export function validateReleaseIdentity({ ref, source, checkedOut, reviewed, receiptReviewed }) { assert.equal(ref, 'refs/heads/main', 'Publication is restricted to main.'); assert.match(source || '', /^[a-f0-9]{40}$/, 'Dispatch source must be immutable.'); diff --git a/tests/release-policy.test.mjs b/tests/release-policy.test.mjs index bbe27b9..1ca0d21 100644 --- a/tests/release-policy.test.mjs +++ b/tests/release-policy.test.mjs @@ -9,3 +9,40 @@ test('publication requires main and exact immutable dispatch, checkout and revie assert.throws(() => validateReleaseIdentity({ ...valid, ...delta })); } }); + +import { RELEASE_GATE_PATHSPEC } from '../scripts/release-policy.mjs'; +import { mkdtempSync, writeFileSync, mkdirSync, rmSync, readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { execFileSync, spawnSync } from 'node:child_process'; + +test('the release gate rejects post-review changes everywhere except release receipts', t => { + const root = mkdtempSync(join(tmpdir(), 'polymorfa-release-gate-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', env: { ...process.env, GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@example.invalid', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@example.invalid' } }).trim(); + git('init', '-q'); + for (const [path, text] of [['.npmrc', 'save-exact=true\n'], ['.nvmrc', '24.15.0\n'], ['tests/a.test.cjs', 'ok\n'], ['n8n/package.json', '{}\n'], ['releases/0.1.0.json', '{}\n'], ['README.md', 'x\n']]) { + mkdirSync(join(root, path, '..'), { recursive: true }); writeFileSync(join(root, path), text); + } + git('add', '-A'); git('commit', '-qm', 'reviewed'); const reviewed = git('rev-parse', 'HEAD'); + const gate = () => spawnSync('git', ['diff', '--quiet', reviewed, 'HEAD', '--', ...RELEASE_GATE_PATHSPEC], { cwd: root }).status; + writeFileSync(join(root, 'releases/0.1.0.json'), '{"reviewedSource":"x"}\n'); git('commit', '-qam', 'receipt'); + assert.equal(gate(), 0, 'a receipt-only change passes'); + for (const path of ['.npmrc', '.nvmrc', 'tests/a.test.cjs', 'README.md', 'n8n/package.json']) { + writeFileSync(join(root, path), 'changed\n'); git('commit', '-qam', 'change ' + path); + assert.equal(gate(), 1, `${path} change is refused`); + git('reset', '-q', '--hard', 'HEAD~1'); + } +}); + +test('the publication workflow gates source before repository code runs with an OIDC token', () => { + const workflow = readFileSync('.github/workflows/publish-n8n.yml', 'utf8'); + const [verify, publish] = workflow.split(/\n publish:\n/); + assert.ok(publish, 'publish is a separate job'); + assert.doesNotMatch(verify, /id-token/); assert.match(publish, /id-token: write/); assert.match(publish, /needs: verify/); + assert.ok(verify.indexOf('node scripts/release-gate.mjs') < verify.indexOf('setup-node'), 'the verify job gates before installing or testing'); + const gateAt = publish.indexOf('Check reviewed source binding'); + assert.ok(gateAt > 0 && gateAt < publish.indexOf('setup-node') && gateAt < publish.indexOf('- run: npm'), 'the publish job gates before Node setup and npm'); + assert.ok(!/node |npm run/.test(publish.slice(publish.indexOf('actions/checkout'), publish.indexOf('setup-node'))), 'the publish gate runs no repository code'); + assert.ok(publish.includes(`git diff --exit-code "$RELEASE_REVIEWED_SOURCE" HEAD -- ${RELEASE_GATE_PATHSPEC.map(p => p === '.' ? '.' : `'${p}'`).join(' ')}`)); +}); From 29b8943f526c8350efa436a5c411906a37d5590f Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:37:32 +0200 Subject: [PATCH 09/14] Fail the generated-file check on files the generator no longer emits Every file in a generated directory must come from the current generator. The check reports stale ones and npm run generate removes them. --- MAINTAINING.md | 4 +++- scripts/generate.mjs | 20 ++++++++++++++++++-- tests/generate.test.mjs | 29 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 tests/generate.test.mjs diff --git a/MAINTAINING.md b/MAINTAINING.md index 9dae562..56b32e4 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -16,7 +16,9 @@ node scripts/check-upstream.mjs --pinned `shared/` owns the action fields, event groups, request mapping and recovery behavior. `npm run generate` copies that code into independently deployable native packages and generates Make functions and Pipedream components. Do not -edit generated files directly. CI rejects stale generated output. +edit generated files directly. CI rejects stale generated output, including +files in generated directories that the generator no longer emits; `npm run +generate` removes them. `contracts/source.json` pins the public API audience and matching SDK source. `coverage.json` identifies each allowed operation and deliberate exclusions. diff --git a/scripts/generate.mjs b/scripts/generate.mjs index ef51e0c..5e3a953 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -1,5 +1,5 @@ -import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; -import { dirname } from 'node:path'; +import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync } from 'node:fs'; +import { dirname, join } from 'node:path'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const { actions, fields, events } = require('../shared/catalog.cjs'); @@ -7,7 +7,9 @@ const core = require('../shared/core.cjs'); const hooks = require('../shared/hooks.cjs'); const check = process.argv.includes('--check'); let drift = false; +const emitted = new Set(); function emit(path, value) { + emitted.add(path); const content = typeof value === 'string' ? value : JSON.stringify(value, null, 2) + '\n'; if (check) { if (!existsSync(path) || readFileSync(path, 'utf8') !== content) { console.error(`Generated file differs: ${path}`); drift = true; } @@ -121,5 +123,19 @@ for (const [key, e] of Object.entries(events)) { const valid = `polymorfaReceive(parameters.receiverName; parameters.receiverValue; '${key}'; headers; body)`; emit(`make/webhooks/${key}/communication.json`, { condition: `{{${valid}}}`, respond: { status: `{{if(${valid}; 200; 401)}}`, type: 'json', body: {} }, output: `{{polymorfaEnvelope(body; '${key}')}}` }); } +// Every file in these directories is generator output. A file the generator no longer emits is stale. +const generatedDirs = ['n8n/shared', 'zapier/shared', 'pipedream/common', 'pipedream/actions', 'pipedream/sources', 'make/functions', 'make/modules', 'make/webhooks', 'make/connections']; +function files(dir) { return existsSync(dir) ? readdirSync(dir, { withFileTypes: true }).flatMap(e => e.isDirectory() ? files(join(dir, e.name)) : [join(dir, e.name)]) : []; } +function prune(dir) { + if (!existsSync(dir)) return; + for (const e of readdirSync(dir, { withFileTypes: true })) if (e.isDirectory()) prune(join(dir, e.name)); + if (!readdirSync(dir).length) rmSync(dir, { recursive: true }); +} +for (const dir of generatedDirs) { + for (const path of files(dir)) if (!emitted.has(path)) { + if (check) { console.error(`Stale generated file: ${path}`); drift = true; } else rmSync(path); + } + if (!check) prune(dir); +} if (drift) process.exitCode = 1; else console.log(check ? 'Generated adapter files are current.' : 'Generated native adapter files.'); diff --git a/tests/generate.test.mjs b/tests/generate.test.mjs new file mode 100644 index 0000000..4013e9b --- /dev/null +++ b/tests/generate.test.mjs @@ -0,0 +1,29 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { cpSync, mkdtempSync, rmSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const inputs = ['shared', 'fixtures', 'contracts', 'LICENSE', 'NOTICE.md', 'scripts/generate.mjs', 'n8n/shared', 'n8n/LICENSE', 'n8n/NOTICE.md', 'zapier/shared', 'pipedream', 'make']; +function copy() { + const root = mkdtempSync(join(tmpdir(), 'polymorfa-generate-')); + for (const path of inputs) cpSync(path, join(root, path), { recursive: true }); + return root; +} +const run = (root, ...args) => spawnSync(process.execPath, ['scripts/generate.mjs', ...args], { cwd: root, encoding: 'utf8' }); + +test('generated-file check fails on stale files the generator no longer emits, and generate removes them', t => { + const root = copy(); t.after(() => rmSync(root, { recursive: true, force: true })); + assert.equal(run(root, '--check').status, 0); + const stale = ['pipedream/actions/removedAction/removedAction.mjs', 'make/modules/removedModule/metadata.json', 'make/functions/polymorfaRemoved.js', 'make/webhooks/messageReceived/extra.json', 'zapier/shared/old.cjs']; + for (const path of stale) { mkdirSync(join(root, path, '..'), { recursive: true }); writeFileSync(join(root, path), 'stale\n'); } + const result = run(root, '--check'); + assert.equal(result.status, 1); + for (const path of stale) assert.match(result.stderr, new RegExp(`Stale generated file: ${path.replace(/[./]/g, '\\$&')}`)); + assert.equal(run(root).status, 0); + for (const path of stale) assert.equal(existsSync(join(root, path)), false); + assert.equal(existsSync(join(root, 'pipedream/actions/removedAction')), false); + assert.equal(existsSync(join(root, 'pipedream/polymorfa.app.mjs')), true, 'hand-written files outside generated directories are kept'); + assert.equal(run(root, '--check').status, 0); +}); From 46dd05298f119ed44f2ae3a3de04c5912d1045ed Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:39:24 +0200 Subject: [PATCH 10/14] Generate a webhook creation nonce per trigger activation The create idempotency key came from a user-typed creation key, so deactivating and reactivating a trigger replayed the receipt for the deleted webhook and the activation failed. Each activation now gets a random nonce from the adapter, saved in provider-private state before the create: n8n node static data and Pipedream $.service.db. Zapier and Make have no state that persists before performSubscribe or attach returns, so the nonce lives for that call. A retry of the same uncertain activation reuses the nonce, so it cannot create a second webhook. Find-before-create and the post-create live check stay. A replayed receipt for a deleted webhook now fails with subscription_receipt_deleted and clears the nonce, so the next activation registers a new webhook. The Webhook Creation Key field is removed from all four platforms. --- CHANGELOG.md | 6 +- MAINTAINING.md | 15 +- README.md | 17 ++- make/functions/polymorfaAdvanced.js | 2 +- make/functions/polymorfaBody.js | 2 +- make/functions/polymorfaCreateKey.js | 6 +- make/functions/polymorfaCreationNonce.js | 5 + make/functions/polymorfaEnvelope.js | 2 +- make/functions/polymorfaHook.js | 2 +- make/functions/polymorfaRequest.js | 2 +- make/functions/polymorfaResult.js | 2 +- make/functions/polymorfaSegment.js | 2 +- make/webhooks/callEnded/attach.json | 5 +- make/webhooks/callEnded/parameters.json | 7 - make/webhooks/messageReceived/attach.json | 5 +- make/webhooks/messageReceived/parameters.json | 7 - make/webhooks/messageState/attach.json | 5 +- make/webhooks/messageState/parameters.json | 7 - make/webhooks/numberState/attach.json | 5 +- make/webhooks/numberState/parameters.json | 7 - n8n/README.md | 10 +- n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts | 11 +- n8n/shared/core.js | 2 +- n8n/shared/hooks.js | 62 +++++--- pipedream/common/core.cjs | 2 +- pipedream/common/hooks.cjs | 62 +++++--- pipedream/polymorfa.app.mjs | 3 +- pipedream/sources/callEnded/callEnded.mjs | 7 +- .../messageReceived/messageReceived.mjs | 7 +- .../sources/messageState/messageState.mjs | 7 +- pipedream/sources/numberState/numberState.mjs | 7 +- scripts/generate.mjs | 11 +- shared/core.cjs | 2 +- shared/hooks.cjs | 62 +++++--- tests/fake-webhooks.cjs | 49 ++++++ tests/hooks.test.cjs | 143 +++++++++++------- tests/make.test.cjs | 17 ++- tests/native.test.cjs | 48 +++++- tests/pipedream.test.mjs | 34 +++++ zapier/index.js | 7 +- zapier/shared/core.cjs | 2 +- zapier/shared/hooks.cjs | 62 +++++--- 42 files changed, 504 insertions(+), 224 deletions(-) create mode 100644 make/functions/polymorfaCreationNonce.js create mode 100644 tests/fake-webhooks.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d68525..26b9217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## Unreleased -- Separate webhook receiver secrets from public marker/idempotency metadata; - use a fresh create identity after a deleted subscription. +- Separate webhook receiver secrets from public marker/idempotency metadata. +- Remove the Webhook Creation Key field from all triggers. Each activation uses + its own adapter-generated nonce, so off/on, n8n test listening and Pipedream + redeploys work, and a receipt for a deleted webhook is refused. - Preserve definite subscription errors and non-JSON uncertain outcomes; normalize callback ownership and native Zapier response headers. - Keep successful n8n batch items when Continue On Fail is enabled. Require diff --git a/MAINTAINING.md b/MAINTAINING.md index 56b32e4..abdafc1 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -59,11 +59,16 @@ git and jq before Node setup, `.npmrc` use or any repository script. Restrict th and require an independent release reviewer before enabling the trusted publisher. These external settings cannot be secured by editable workflow code alone. -Webhook marker names, receiver verifiers and create-key seeds now use separate -HMAC domains. Each new activation adds its own nonce to the create key so a -deleted hook cannot be recovered from an old create receipt. This is a change -before the first release; remove old development subscriptions with their recorded -ownership metadata before recreating them. No production migration is implied. +Webhook marker names, receiver verifiers and create-key seeds use separate HMAC +domains. The create key is `HMAC(markerKey, nonce)`. `hooks.activate` generates +a random nonce per activation and saves it in provider-private state before the +create: n8n node static data, Pipedream `$.service.db`, Zapier and Make in the +single `performSubscribe` call or attach run (neither platform offers state that +persists before those return). An uncertain retry of the same activation reuses +the nonce; a confirmed activation, a deletion or a replayed receipt for a deleted +webhook (`subscription_receipt_deleted`) clears it. Find-before-create and the +post-create live-ownership check stay in place. Users no longer enter a webhook +creation key. This is a change before the first release; no migration is implied. Keep API credentials in each provider's credential store. Webhook receivers use an authenticated per-subscription header supported by the public subscription diff --git a/README.md b/README.md index 488a18a..6c18fe4 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,16 @@ a separate workflow engine or stores your third-party app connections. Documentation: [docs.polymorfa.com](https://docs.polymorfa.com). -Webhook triggers save two values in their native configuration. The subscription -key identifies the trigger and stays stable. The webhook creation key identifies -one registration attempt and stays the same across uncertain retries. Change the -creation key only after confirming that the previous registration was deleted. -Reusing a deleted registration's key fails reconciliation instead of reporting a -working trigger. An old or uncertain registration needs inventory reconciliation -before a new creation key is used. Native editor acceptance remains pending. +Webhook triggers ask for one value: a subscription key that identifies the +trigger and stays stable. Each activation registers its own webhook with a +creation nonce the adapter generates and keeps in the platform's private trigger +state. A retry of an uncertain activation reuses that nonce, so it cannot create +a second webhook. Turning a trigger off and on again uses a new nonce. Before +creating, every adapter looks for its owned webhook and adopts it, and after +creating it confirms the webhook is live. If the API replays a receipt for a +webhook that was since deleted, activation fails with +`subscription_receipt_deleted` instead of reporting a working trigger; the next +activation registers a new webhook. Native editor acceptance remains pending. Build and maintenance: [MAINTAINING.md](MAINTAINING.md). Provider source: [n8n](n8n/README.md), [Make](make/README.md), diff --git a/make/functions/polymorfaAdvanced.js b/make/functions/polymorfaAdvanced.js index feb0470..e9cb6d6 100644 --- a/make/functions/polymorfaAdvanced.js +++ b/make/functions/polymorfaAdvanced.js @@ -1,7 +1,7 @@ function polymorfaAdvanced(p, auth) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaBody.js b/make/functions/polymorfaBody.js index 131e6ff..7750229 100644 --- a/make/functions/polymorfaBody.js +++ b/make/functions/polymorfaBody.js @@ -1,7 +1,7 @@ function polymorfaBody(action, p) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaCreateKey.js b/make/functions/polymorfaCreateKey.js index 5b1a567..0576c95 100644 --- a/make/functions/polymorfaCreateKey.js +++ b/make/functions/polymorfaCreateKey.js @@ -1,5 +1,5 @@ -function polymorfaCreateKey(key, registrationKey) { +function polymorfaCreateKey(key, nonce) { -if (!/^[a-f0-9]{64}$/.test(key) || typeof registrationKey !== 'string' || !registrationKey.trim() || registrationKey.length > 200) throw new Error('Missing webhook creation key.'); -return 'integration-create-' + iml.sha256(registrationKey, 'hex', key); +if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid webhook creation nonce.'); +return 'integration-create-' + iml.sha256(nonce, 'hex', key); } diff --git a/make/functions/polymorfaCreationNonce.js b/make/functions/polymorfaCreationNonce.js new file mode 100644 index 0000000..2cafa18 --- /dev/null +++ b/make/functions/polymorfaCreationNonce.js @@ -0,0 +1,5 @@ +function polymorfaCreationNonce(url) { + +if (typeof url !== 'string' || !url) throw new Error('Invalid callback.'); +return iml.sha256(url + ':' + Date.now() + ':' + Math.random() + ':' + Math.random(), 'hex'); +} diff --git a/make/functions/polymorfaEnvelope.js b/make/functions/polymorfaEnvelope.js index c096330..19385f7 100644 --- a/make/functions/polymorfaEnvelope.js +++ b/make/functions/polymorfaEnvelope.js @@ -1,7 +1,7 @@ function polymorfaEnvelope(body, group) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaHook.js b/make/functions/polymorfaHook.js index 9cc23c2..088c890 100644 --- a/make/functions/polymorfaHook.js +++ b/make/functions/polymorfaHook.js @@ -1,7 +1,7 @@ function polymorfaHook(auth, group, subscriptionKey) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaRequest.js b/make/functions/polymorfaRequest.js index cec9d4c..6104e45 100644 --- a/make/functions/polymorfaRequest.js +++ b/make/functions/polymorfaRequest.js @@ -1,7 +1,7 @@ function polymorfaRequest(action, p, auth) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaResult.js b/make/functions/polymorfaResult.js index 5a8c1fe..7535b8b 100644 --- a/make/functions/polymorfaResult.js +++ b/make/functions/polymorfaResult.js @@ -1,7 +1,7 @@ function polymorfaResult(status, headers, body) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaSegment.js b/make/functions/polymorfaSegment.js index a47c4ef..302c696 100644 --- a/make/functions/polymorfaSegment.js +++ b/make/functions/polymorfaSegment.js @@ -1,7 +1,7 @@ function polymorfaSegment(value) { class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/webhooks/callEnded/attach.json b/make/webhooks/callEnded/attach.json index c79ecc5..28a21c9 100644 --- a/make/webhooks/callEnded/attach.json +++ b/make/webhooks/callEnded/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'callEnded'; parameters.subscriptionKey)}}", + "nonce": "{{polymorfaCreationNonce(webhook.url)}}" } } }, @@ -16,7 +17,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'callEnded'; parameters.subscriptionKey); 'key'); temp.nonce)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/callEnded/parameters.json b/make/webhooks/callEnded/parameters.json index 2bd958b..73eeef1 100644 --- a/make/webhooks/callEnded/parameters.json +++ b/make/webhooks/callEnded/parameters.json @@ -5,12 +5,5 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." - }, - { - "name": "registrationKey", - "label": "Webhook Creation Key", - "type": "text", - "required": true, - "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/messageReceived/attach.json b/make/webhooks/messageReceived/attach.json index 89d4100..45d61c3 100644 --- a/make/webhooks/messageReceived/attach.json +++ b/make/webhooks/messageReceived/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageReceived'; parameters.subscriptionKey)}}", + "nonce": "{{polymorfaCreationNonce(webhook.url)}}" } } }, @@ -16,7 +17,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageReceived'; parameters.subscriptionKey); 'key'); temp.nonce)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/messageReceived/parameters.json b/make/webhooks/messageReceived/parameters.json index 2bd958b..73eeef1 100644 --- a/make/webhooks/messageReceived/parameters.json +++ b/make/webhooks/messageReceived/parameters.json @@ -5,12 +5,5 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." - }, - { - "name": "registrationKey", - "label": "Webhook Creation Key", - "type": "text", - "required": true, - "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/messageState/attach.json b/make/webhooks/messageState/attach.json index 2f6c909..f8118b8 100644 --- a/make/webhooks/messageState/attach.json +++ b/make/webhooks/messageState/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'messageState'; parameters.subscriptionKey)}}", + "nonce": "{{polymorfaCreationNonce(webhook.url)}}" } } }, @@ -16,7 +17,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'messageState'; parameters.subscriptionKey); 'key'); temp.nonce)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/messageState/parameters.json b/make/webhooks/messageState/parameters.json index 2bd958b..73eeef1 100644 --- a/make/webhooks/messageState/parameters.json +++ b/make/webhooks/messageState/parameters.json @@ -5,12 +5,5 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." - }, - { - "name": "registrationKey", - "label": "Webhook Creation Key", - "type": "text", - "required": true, - "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/make/webhooks/numberState/attach.json b/make/webhooks/numberState/attach.json index 6f54116..3210f1b 100644 --- a/make/webhooks/numberState/attach.json +++ b/make/webhooks/numberState/attach.json @@ -7,7 +7,8 @@ }, "response": { "temp": { - "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}" + "ownedId": "{{polymorfaFindHook(body; connection; webhook.url; 'numberState'; parameters.subscriptionKey)}}", + "nonce": "{{polymorfaCreationNonce(webhook.url)}}" } } }, @@ -16,7 +17,7 @@ "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", "method": "POST", "headers": { - "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key'); parameters.registrationKey)}}" + "Idempotency-Key": "{{polymorfaCreateKey(get(polymorfaHook(connection; 'numberState'; parameters.subscriptionKey); 'key'); temp.nonce)}}" }, "body": { "url": "{{webhook.url}}", diff --git a/make/webhooks/numberState/parameters.json b/make/webhooks/numberState/parameters.json index 2bd958b..73eeef1 100644 --- a/make/webhooks/numberState/parameters.json +++ b/make/webhooks/numberState/parameters.json @@ -5,12 +5,5 @@ "type": "text", "required": true, "help": "Unique stable label for this webhook. Events are project-wide." - }, - { - "name": "registrationKey", - "label": "Webhook Creation Key", - "type": "text", - "required": true, - "help": "Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration." } ] diff --git a/n8n/README.md b/n8n/README.md index 846bb24..c013828 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -35,10 +35,12 @@ failed items return an error paired to the original input. Later items continue. Use the saved write identity when handling an error; do not replay successful items with new keys. -Give each trigger a unique, stable **Subscription Key**. Webhook subscriptions -are project-wide. Filtering a workflow after receipt does not prevent disclosure -to n8n. Dedupe by event `id` before downstream effects; repeated delivery is -possible. Delete/disable the owned webhook before revoking its credential. +Give each trigger a unique, stable **Subscription Key**. The node registers a +new webhook on each activation and keeps its creation state in workflow static +data, so test listening, deactivation and reactivation need no extra field. +Webhook subscriptions are project-wide. Filtering a workflow after receipt does +not prevent disclosure to n8n. Dedupe by event `id` before downstream effects; +repeated delivery is possible. Delete/disable the owned webhook before revoking its credential. n8n executes your workflow under its own hosting and retention settings. Choosing a Polymorfa EU or Brazil region does not place n8n's processing in that region. diff --git a/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts index 4d965ab..215e4de 100644 --- a/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts +++ b/n8n/nodes/Polymorfa/PolymorfaTrigger.node.ts @@ -1,7 +1,7 @@ import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow'; import { events } from '../../shared/catalog'; -import { subscribe, unsubscribe, authenticate, envelope, find, identity } from '../../shared/hooks'; +import { activate, unsubscribe, authenticate, envelope, find, identity } from '../../shared/hooks'; import { transport } from './transport'; export class PolymorfaTrigger implements INodeType { @@ -14,7 +14,6 @@ export class PolymorfaTrigger implements INodeType { properties: [ { displayName: 'Event', name: 'eventGroup', type: 'options', default: 'messageReceived', options: Object.entries(events).map(([value, event]) => ({ name: event.label, value, description: event.description })) }, { displayName: 'Subscription Key', name: 'subscriptionKey', type: 'string', required: true, default: '', description: 'A unique stable label for this trigger. Keep it on reactivation; use a different value for another trigger.' }, - { displayName: 'Webhook Creation Key', name: 'registrationKey', type: 'string', required: true, default: '', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, { displayName: 'Events are project-wide. Filtering after receipt does not limit which Numbers disclose events to n8n.', name: 'scopeNotice', type: 'notice', default: '' }, ], }; @@ -28,8 +27,11 @@ export class PolymorfaTrigger implements INodeType { }, async create(this: IHookFunctions) { const auth = await this.getCredentials('polymorfaApi'); - const hook = await subscribe(auth, this.getNodeWebhookUrl('default')!, this.getNodeParameter('eventGroup') as string, this.getNodeParameter('subscriptionKey') as string, transport(this), 'n8n', this.getNodeParameter('registrationKey') as string); - this.getWorkflowStaticData('node').subscription = hook; return true; + const data = this.getWorkflowStaticData('node'); + // Node static data keeps this activation's creation nonce private to n8n. + const store = { get: () => data.pendingCreation, set: (value: unknown) => { if (value) data.pendingCreation = value; else delete data.pendingCreation; } }; + const hook = await activate(auth, this.getNodeWebhookUrl('default')!, this.getNodeParameter('eventGroup') as string, this.getNodeParameter('subscriptionKey') as string, transport(this), 'n8n', store); + data.subscription = hook; return true; }, async delete(this: IHookFunctions) { const data = this.getWorkflowStaticData('node'); @@ -38,6 +40,7 @@ export class PolymorfaTrigger implements INodeType { await unsubscribe(await this.getCredentials('polymorfaApi'), owned.id, owned.url, owned.group, owned.subscriptionKey, transport(this), 'n8n', owned.markerName); delete data.subscription; } + delete data.pendingCreation; return true; }, } }; diff --git a/n8n/shared/core.js b/n8n/shared/core.js index 67b78c8..59522cc 100644 --- a/n8n/shared/core.js +++ b/n8n/shared/core.js @@ -7,7 +7,7 @@ const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/n8n/shared/hooks.js b/n8n/shared/hooks.js index cdd4ee3..5ab9b17 100644 --- a/n8n/shared/hooks.js +++ b/n8n/shared/hooks.js @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.js'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.js'); function identity(auth, group, subscriptionKey) { @@ -20,10 +20,10 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } -function creationKey(markerKey, registrationKey) { - required(registrationKey, 'registration_key'); - if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); - return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +const NONCE = /^[A-Za-z0-9_-]{16,128}$/; +function creationKey(markerKey, nonce) { + if (typeof nonce !== 'string' || !NONCE.test(nonce)) throw new IntegrationError('invalid_creation_nonce'); + return 'integration-create-' + createHmac('sha256', markerKey).update(nonce).digest('hex'); } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); @@ -59,22 +59,18 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, nonce) { url = callback(url); const marker = identity(auth, group, subscriptionKey); - const createKey = creationKey(marker.key, registrationKey); + const createKey = creationKey(marker.key, nonce); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + let r; try { - // Saved trigger configuration owns this generation across uncertain retries. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); - if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - // An idempotency receipt may outlive its deleted webhook. Verify live ownership. - const live = await find(auth, url, group, subscriptionKey, transport, provider); - if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); - return record(live); + // The adapter keeps this activation's nonce in private state, so an uncertain retry reuses the key. + r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -82,6 +78,37 @@ async function subscribe(auth, url, group, subscriptionKey, transport, provider, if (recovered) return record(recovered); throw new IntegrationError('subscription_requires_reconciliation'); } + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (live && live.id === r.data.webhook.id) return record(live); + if (!live) { + try { await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(r.data.webhook.id, 'webhook_id')}`); } + catch (error) { if (error.status === 404) throw new IntegrationError('subscription_receipt_deleted'); } + } + throw new IntegrationError('subscription_receipt_requires_reconciliation'); +} +function creation(url, group, subscriptionKey) { + return { nonce: randomUUID(), url: callback(url), group, subscriptionKey }; +} +// Store: provider-private { get(), set(value) } for this trigger's pending creation. +async function activate(auth, url, group, subscriptionKey, transport, provider, store) { + const target = callback(url); + let pending = await store.get(); + if (!pending || pending.url !== target || pending.group !== group || pending.subscriptionKey !== subscriptionKey || typeof pending.nonce !== 'string' || !NONCE.test(pending.nonce)) { + pending = creation(target, group, subscriptionKey); + } + // Save before the create so an uncertain retry of this activation sends the same key. + await store.set(pending); + let owned; + try { owned = await subscribe(auth, target, group, subscriptionKey, transport, provider, pending.nonce); } + catch (error) { + // The receipt names a confirmed-deleted webhook. The next activation needs a new nonce. + if (error.code === 'subscription_receipt_deleted') await store.set(null); + throw error; + } + await store.set(null); + return owned; } async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; @@ -92,9 +119,10 @@ async function unsubscribe(auth, id, url, group, subscriptionKey, transport, pro if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); } -function authenticate(auth, group, subscriptionKey, incomingHeaders) { +function authenticate(auth, group, subscriptionKey, incomingHeaders, prefixes = ['']) { const expected = identity(auth, group, subscriptionKey); - const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + const names = prefixes.map(p => (p + expected.name).toLowerCase()); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => names.includes(k.toLowerCase())); if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); } @@ -103,4 +131,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, creation, owns, find, subscribe, activate, unsubscribe, authenticate, envelope }; diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs index 67b78c8..59522cc 100644 --- a/pipedream/common/core.cjs +++ b/pipedream/common/core.cjs @@ -7,7 +7,7 @@ const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/pipedream/common/hooks.cjs b/pipedream/common/hooks.cjs index ff5cf15..f15231e 100644 --- a/pipedream/common/hooks.cjs +++ b/pipedream/common/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -20,10 +20,10 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } -function creationKey(markerKey, registrationKey) { - required(registrationKey, 'registration_key'); - if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); - return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +const NONCE = /^[A-Za-z0-9_-]{16,128}$/; +function creationKey(markerKey, nonce) { + if (typeof nonce !== 'string' || !NONCE.test(nonce)) throw new IntegrationError('invalid_creation_nonce'); + return 'integration-create-' + createHmac('sha256', markerKey).update(nonce).digest('hex'); } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); @@ -59,22 +59,18 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, nonce) { url = callback(url); const marker = identity(auth, group, subscriptionKey); - const createKey = creationKey(marker.key, registrationKey); + const createKey = creationKey(marker.key, nonce); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + let r; try { - // Saved trigger configuration owns this generation across uncertain retries. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); - if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - // An idempotency receipt may outlive its deleted webhook. Verify live ownership. - const live = await find(auth, url, group, subscriptionKey, transport, provider); - if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); - return record(live); + // The adapter keeps this activation's nonce in private state, so an uncertain retry reuses the key. + r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -82,6 +78,37 @@ async function subscribe(auth, url, group, subscriptionKey, transport, provider, if (recovered) return record(recovered); throw new IntegrationError('subscription_requires_reconciliation'); } + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (live && live.id === r.data.webhook.id) return record(live); + if (!live) { + try { await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(r.data.webhook.id, 'webhook_id')}`); } + catch (error) { if (error.status === 404) throw new IntegrationError('subscription_receipt_deleted'); } + } + throw new IntegrationError('subscription_receipt_requires_reconciliation'); +} +function creation(url, group, subscriptionKey) { + return { nonce: randomUUID(), url: callback(url), group, subscriptionKey }; +} +// Store: provider-private { get(), set(value) } for this trigger's pending creation. +async function activate(auth, url, group, subscriptionKey, transport, provider, store) { + const target = callback(url); + let pending = await store.get(); + if (!pending || pending.url !== target || pending.group !== group || pending.subscriptionKey !== subscriptionKey || typeof pending.nonce !== 'string' || !NONCE.test(pending.nonce)) { + pending = creation(target, group, subscriptionKey); + } + // Save before the create so an uncertain retry of this activation sends the same key. + await store.set(pending); + let owned; + try { owned = await subscribe(auth, target, group, subscriptionKey, transport, provider, pending.nonce); } + catch (error) { + // The receipt names a confirmed-deleted webhook. The next activation needs a new nonce. + if (error.code === 'subscription_receipt_deleted') await store.set(null); + throw error; + } + await store.set(null); + return owned; } async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; @@ -92,9 +119,10 @@ async function unsubscribe(auth, id, url, group, subscriptionKey, transport, pro if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); } -function authenticate(auth, group, subscriptionKey, incomingHeaders) { +function authenticate(auth, group, subscriptionKey, incomingHeaders, prefixes = ['']) { const expected = identity(auth, group, subscriptionKey); - const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + const names = prefixes.map(p => (p + expected.name).toLowerCase()); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => names.includes(k.toLowerCase())); if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); } @@ -103,4 +131,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, creation, owns, find, subscribe, activate, unsubscribe, authenticate, envelope }; diff --git a/pipedream/polymorfa.app.mjs b/pipedream/polymorfa.app.mjs index 56d1fd5..29f56b8 100644 --- a/pipedream/polymorfa.app.mjs +++ b/pipedream/polymorfa.app.mjs @@ -16,7 +16,8 @@ export default { }; }, execute($, action, input) { return core.execute(action, input, this.auth(), this.transport($), 'pipedream'); }, - subscribe($, url, group, key, registrationKey) { return hooks.subscribe(this.auth(), url, group, key, this.transport($), 'pipedream', registrationKey); }, + // store: the source's $.service.db view of this activation's pending creation nonce. + activate($, url, group, key, store) { return hooks.activate(this.auth(), url, group, key, this.transport($), 'pipedream', store); }, unsubscribe($, owned) { return hooks.unsubscribe(this.auth(), owned.id, owned.url, owned.group, owned.subscriptionKey, this.transport($), 'pipedream', owned.markerName); }, authenticate(group, key, headers) { return hooks.authenticate(this.auth(), group, key, headers); }, envelope(body, group) { return hooks.envelope(body, group); }, diff --git a/pipedream/sources/callEnded/callEnded.mjs b/pipedream/sources/callEnded/callEnded.mjs index 9d7dca5..dfe12a7 100644 --- a/pipedream/sources/callEnded/callEnded.mjs +++ b/pipedream/sources/callEnded/callEnded.mjs @@ -4,16 +4,17 @@ export default { key: "polymorfa-callEnded", name: "Call Ended", description: "Triggers when a call ends.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, - registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "callEnded", this.subscriptionKey, this.registrationKey); + const store = { get: () => this.db.get('pendingCreation'), set: value => this.db.set('pendingCreation', value || null) }; + const owned = await this.polymorfa.activate(undefined, this.http.endpoint, "callEnded", this.subscriptionKey, store); this.db.set('ownedSubscription', owned); }, async deactivate() { const owned = this.db.get('ownedSubscription'); if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + this.db.set('pendingCreation', null); }, }, async run(event) { diff --git a/pipedream/sources/messageReceived/messageReceived.mjs b/pipedream/sources/messageReceived/messageReceived.mjs index fa10ca9..963c23d 100644 --- a/pipedream/sources/messageReceived/messageReceived.mjs +++ b/pipedream/sources/messageReceived/messageReceived.mjs @@ -4,16 +4,17 @@ export default { key: "polymorfa-messageReceived", name: "New Message", description: "Triggers when a message is received.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, - registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageReceived", this.subscriptionKey, this.registrationKey); + const store = { get: () => this.db.get('pendingCreation'), set: value => this.db.set('pendingCreation', value || null) }; + const owned = await this.polymorfa.activate(undefined, this.http.endpoint, "messageReceived", this.subscriptionKey, store); this.db.set('ownedSubscription', owned); }, async deactivate() { const owned = this.db.get('ownedSubscription'); if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + this.db.set('pendingCreation', null); }, }, async run(event) { diff --git a/pipedream/sources/messageState/messageState.mjs b/pipedream/sources/messageState/messageState.mjs index 03d9842..8aba13e 100644 --- a/pipedream/sources/messageState/messageState.mjs +++ b/pipedream/sources/messageState/messageState.mjs @@ -4,16 +4,17 @@ export default { key: "polymorfa-messageState", name: "Message State Changed", description: "Triggers when a message is sent, acknowledged, failed or updated.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, - registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "messageState", this.subscriptionKey, this.registrationKey); + const store = { get: () => this.db.get('pendingCreation'), set: value => this.db.set('pendingCreation', value || null) }; + const owned = await this.polymorfa.activate(undefined, this.http.endpoint, "messageState", this.subscriptionKey, store); this.db.set('ownedSubscription', owned); }, async deactivate() { const owned = this.db.get('ownedSubscription'); if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + this.db.set('pendingCreation', null); }, }, async run(event) { diff --git a/pipedream/sources/numberState/numberState.mjs b/pipedream/sources/numberState/numberState.mjs index ebbc6fd..cff0dfd 100644 --- a/pipedream/sources/numberState/numberState.mjs +++ b/pipedream/sources/numberState/numberState.mjs @@ -4,16 +4,17 @@ export default { key: "polymorfa-numberState", name: "Number State Changed", description: "Triggers when a Number changes state, connects or logs out.", version: '0.1.0', type: 'source', dedupe: 'unique', props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true }, - subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' }, - registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } }, + subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } }, hooks: { async activate() { - const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, "numberState", this.subscriptionKey, this.registrationKey); + const store = { get: () => this.db.get('pendingCreation'), set: value => this.db.set('pendingCreation', value || null) }; + const owned = await this.polymorfa.activate(undefined, this.http.endpoint, "numberState", this.subscriptionKey, store); this.db.set('ownedSubscription', owned); }, async deactivate() { const owned = this.db.get('ownedSubscription'); if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); } + this.db.set('pendingCreation', null); }, }, async run(event) { diff --git a/scripts/generate.mjs b/scripts/generate.mjs index 5e3a953..599befe 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -47,7 +47,7 @@ for (const [key, action] of Object.entries(actions)) { } emit(`pipedream/actions/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(action.label)}, description: ${JSON.stringify(action.description)},\n version: '0.1.0', type: 'action',\n props: { polymorfa, ...${JSON.stringify(props, null, 2)} },\n async run({ $ }) {\n const input = Object.fromEntries(${JSON.stringify(action.fields)}.map(key => [key, this[key]]));\n return this.polymorfa.execute($, ${JSON.stringify(key)}, input);\n },\n};\n`); } -for (const [key, event] of Object.entries(events)) emit(`pipedream/sources/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(event.label)}, description: ${JSON.stringify(event.description)},\n version: '0.1.0', type: 'source', dedupe: 'unique',\n props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true },\n subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' },\n registrationKey: { type: 'string', label: 'Webhook Creation Key', description: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' } },\n hooks: {\n async activate() {\n const owned = await this.polymorfa.subscribe(undefined, this.http.endpoint, ${JSON.stringify(key)}, this.subscriptionKey, this.registrationKey);\n this.db.set('ownedSubscription', owned);\n },\n async deactivate() {\n const owned = this.db.get('ownedSubscription');\n if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); }\n },\n },\n async run(event) {\n if (!this.polymorfa.authenticate(${JSON.stringify(key)}, this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; }\n let data;\n try { data = this.polymorfa.envelope(event.body, ${JSON.stringify(key)}); } catch { this.http.respond({ status: 400 }); return; }\n this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) });\n this.http.respond({ status: 200 });\n },\n};\n`); +for (const [key, event] of Object.entries(events)) emit(`pipedream/sources/${key}/${key}.mjs`, `// Generated from shared/catalog.cjs.\nimport polymorfa from '../../polymorfa.app.mjs';\nexport default {\n key: ${JSON.stringify('polymorfa-' + key)}, name: ${JSON.stringify(event.label)}, description: ${JSON.stringify(event.description)},\n version: '0.1.0', type: 'source', dedupe: 'unique',\n props: { polymorfa, db: '$.service.db', http: { type: '$.interface.http', customResponse: true },\n subscriptionKey: { type: 'string', label: 'Subscription Key', description: 'Unique stable label for this source. Events are project-wide.' } },\n hooks: {\n async activate() {\n const store = { get: () => this.db.get('pendingCreation'), set: value => this.db.set('pendingCreation', value || null) };\n const owned = await this.polymorfa.activate(undefined, this.http.endpoint, ${JSON.stringify(key)}, this.subscriptionKey, store);\n this.db.set('ownedSubscription', owned);\n },\n async deactivate() {\n const owned = this.db.get('ownedSubscription');\n if (owned) { await this.polymorfa.unsubscribe(undefined, owned); this.db.set('ownedSubscription', null); }\n this.db.set('pendingCreation', null);\n },\n },\n async run(event) {\n if (!this.polymorfa.authenticate(${JSON.stringify(key)}, this.subscriptionKey, event.headers)) { this.http.respond({ status: 401 }); return; }\n let data;\n try { data = this.polymorfa.envelope(event.body, ${JSON.stringify(key)}); } catch { this.http.respond({ status: 400 }); return; }\n this.$emit(data, { id: data.id, summary: data.event, ts: Date.parse(data.timestamp) });\n this.http.respond({ status: 200 });\n },\n};\n`); // Make custom IML functions reuse the exact shared request/response behavior. // Every function fits Make's 5,000-character limit. They require provider enablement. @@ -64,7 +64,9 @@ imlFunction('polymorfaRequest', 'action, p, auth', `${errorClass}\nconst ORIGIN imlFunction('polymorfaResult', 'status, headers, body', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body });'); imlFunction('polymorfaSegment', 'value', `${errorClass}\n${core.required}\n${core.segment}`, "return segment(value, 'resource_id');"); imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst derive = label => iml.sha256(label + ':' + scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + derive('marker').slice(0, 24), value: derive('receiver'), key: derive('create') };`); -imlFunction('polymorfaCreateKey', 'key, registrationKey', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof registrationKey !== 'string' || !registrationKey.trim() || registrationKey.length > 200) throw new Error('Missing webhook creation key.');\nreturn 'integration-create-' + iml.sha256(registrationKey, 'hex', key);`); +imlFunction('polymorfaCreateKey', 'key, nonce', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid webhook creation nonce.');\nreturn 'integration-create-' + iml.sha256(nonce, 'hex', key);`); +// One nonce per attach. It is unique, not secret: the create key also needs the derived marker key. +imlFunction('polymorfaCreationNonce', 'url', '', `if (typeof url !== 'string' || !url) throw new Error('Invalid callback.');\nreturn iml.sha256(url + ':' + Date.now() + ':' + Math.random() + ':' + Math.random(), 'hex');`); imlFunction('polymorfaEnvelope', 'body, group', `${errorClass}\nconst events = ${JSON.stringify(events)};\n${hooks.envelope}`, 'return envelope(body, group);'); imlFunction('polymorfaLiveHook', 'body, auth, url, group, key, expectedId', '', `const id = iml.polymorfaFindHook(body, auth, url, group, key);\nif (!id || id !== expectedId) throw new Error('Subscription receipt needs reconciliation.');\nreturn id;`); imlFunction('polymorfaCallback', 'value', '', `if (typeof value !== 'string' || /[\\s\\\\\\x00-\\x1f\\x7f]/.test(value)) throw new Error('Invalid callback.');\nconst parts = value.match(/^https:\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?$/i);\nif (!parts || parts[1].includes('@')) throw new Error('Invalid callback.');\nreturn 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || '');`); @@ -106,14 +108,13 @@ for (const [key, e] of Object.entries(events)) { emit(`make/modules/${key}/interface.json`, schemas.reduce((all, schema) => merge(all, interfaceFields(schema, specs.messaging)), [])); emit(`make/webhooks/${key}/parameters.json`, [ { name: 'subscriptionKey', label: 'Subscription Key', type: 'text', required: true, help: 'Unique stable label for this webhook. Events are project-wide.' }, - { name: 'registrationKey', label: 'Webhook Creation Key', type: 'text', required: true, help: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, ]); emit(`make/webhooks/${key}/metadata.json`, { type: 'dedicated', connection: 'project', label: e.label }); const marker = `polymorfaHook(connection; '${key}'; parameters.subscriptionKey)`; const markerField = field => `{{get(${marker}; '${field}')}}`; emit(`make/webhooks/${key}/attach.json`, [ - { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}` } } }, - { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); parameters.registrationKey)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { temp: { createdId: '{{body.data.webhook.id}}' } } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}`, nonce: '{{polymorfaCreationNonce(webhook.url)}}' } } }, + { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); temp.nonce)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { temp: { createdId: '{{body.data.webhook.id}}' } } }, { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { data: { externalHookId: `{{polymorfaLiveHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}`, ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, ]); emit(`make/webhooks/${key}/detach.json`, [ diff --git a/shared/core.cjs b/shared/core.cjs index a71de62..72ec64e 100644 --- a/shared/core.cjs +++ b/shared/core.cjs @@ -6,7 +6,7 @@ const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/shared/hooks.cjs b/shared/hooks.cjs index c7a9ff8..c28a92b 100644 --- a/shared/hooks.cjs +++ b/shared/hooks.cjs @@ -1,6 +1,6 @@ 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -19,10 +19,10 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } -function creationKey(markerKey, registrationKey) { - required(registrationKey, 'registration_key'); - if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); - return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +const NONCE = /^[A-Za-z0-9_-]{16,128}$/; +function creationKey(markerKey, nonce) { + if (typeof nonce !== 'string' || !NONCE.test(nonce)) throw new IntegrationError('invalid_creation_nonce'); + return 'integration-create-' + createHmac('sha256', markerKey).update(nonce).digest('hex'); } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); @@ -58,22 +58,18 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, nonce) { url = callback(url); const marker = identity(auth, group, subscriptionKey); - const createKey = creationKey(marker.key, registrationKey); + const createKey = creationKey(marker.key, nonce); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + let r; try { - // Saved trigger configuration owns this generation across uncertain retries. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); - if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - // An idempotency receipt may outlive its deleted webhook. Verify live ownership. - const live = await find(auth, url, group, subscriptionKey, transport, provider); - if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); - return record(live); + // The adapter keeps this activation's nonce in private state, so an uncertain retry reuses the key. + r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -81,6 +77,37 @@ async function subscribe(auth, url, group, subscriptionKey, transport, provider, if (recovered) return record(recovered); throw new IntegrationError('subscription_requires_reconciliation'); } + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (live && live.id === r.data.webhook.id) return record(live); + if (!live) { + try { await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(r.data.webhook.id, 'webhook_id')}`); } + catch (error) { if (error.status === 404) throw new IntegrationError('subscription_receipt_deleted'); } + } + throw new IntegrationError('subscription_receipt_requires_reconciliation'); +} +function creation(url, group, subscriptionKey) { + return { nonce: randomUUID(), url: callback(url), group, subscriptionKey }; +} +// Store: provider-private { get(), set(value) } for this trigger's pending creation. +async function activate(auth, url, group, subscriptionKey, transport, provider, store) { + const target = callback(url); + let pending = await store.get(); + if (!pending || pending.url !== target || pending.group !== group || pending.subscriptionKey !== subscriptionKey || typeof pending.nonce !== 'string' || !NONCE.test(pending.nonce)) { + pending = creation(target, group, subscriptionKey); + } + // Save before the create so an uncertain retry of this activation sends the same key. + await store.set(pending); + let owned; + try { owned = await subscribe(auth, target, group, subscriptionKey, transport, provider, pending.nonce); } + catch (error) { + // The receipt names a confirmed-deleted webhook. The next activation needs a new nonce. + if (error.code === 'subscription_receipt_deleted') await store.set(null); + throw error; + } + await store.set(null); + return owned; } async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; @@ -91,9 +118,10 @@ async function unsubscribe(auth, id, url, group, subscriptionKey, transport, pro if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); } -function authenticate(auth, group, subscriptionKey, incomingHeaders) { +function authenticate(auth, group, subscriptionKey, incomingHeaders, prefixes = ['']) { const expected = identity(auth, group, subscriptionKey); - const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + const names = prefixes.map(p => (p + expected.name).toLowerCase()); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => names.includes(k.toLowerCase())); if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); } @@ -102,4 +130,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, creation, owns, find, subscribe, activate, unsubscribe, authenticate, envelope }; diff --git a/tests/fake-webhooks.cjs b/tests/fake-webhooks.cjs new file mode 100644 index 0000000..3b00476 --- /dev/null +++ b/tests/fake-webhooks.cjs @@ -0,0 +1,49 @@ +'use strict'; +// Synthetic project-webhook API for lifecycle tests. It models idempotent create receipts, +// which the real API replays for 24 hours, including receipts for since-deleted webhooks. +const assert = require('node:assert/strict'); +const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; +function server(credentials = auth) { + const rows = []; const calls = []; const receipts = new Map(); + let lose = 0; let hide = 0; let created = 0; + const api = { + rows, calls, receipts, + // The next create is accepted but its response is lost. + loseNextCreate() { lose++; }, + // The next n inventory reads omit the newest row, as an eventually consistent list may. + hideNewest(n) { hide = n; }, + posts() { return calls.filter(r => r.method === 'POST'); }, + async request(r) { + calls.push(r); + assert.equal(new URL(r.url).origin, 'https://api.polymorfa.com'); + assert.equal(r.headers.Authorization, `Bearer ${credentials.apiKey}`); + if (r.method === 'GET' && /\/webhooks$/.test(new URL(r.url).pathname)) { + let data = rows.slice(); + if (hide > 0) { hide--; data = data.slice(0, -1); } + return { status: 200, headers: {}, body: { data, page: { hasMore: false, nextCursor: null } } }; + } + if (r.method === 'POST') { + const key = r.headers['Idempotency-Key']; + assert.ok(key, 'create requires an idempotency key'); + if (receipts.has(key)) { + const receipt = receipts.get(key); + assert.deepEqual(r.body, receipt.request, 'replayed create must send the same request'); + return { ...receipt.response, body: { data: { ...receipt.response.body.data, idempotency: { key, replayed: true } } } }; + } + const row = { ...r.body, owner: 'project', projectId: credentials.projectId, id: `synthetic-hook-${++created}`, headers: r.body.headers.map(h => ({ name: h.name })) }; + rows.push(row); + const response = { status: 201, headers: {}, body: { data: { webhook: { ...row }, secret: 'SYNTHETIC-ONE-TIME-SIGNING-SECRET' } } }; + receipts.set(key, { request: r.body, response }); + if (lose > 0) { lose--; throw new Error('Response lost after accepted create'); } + return response; + } + const row = rows.find(row => new URL(r.url).pathname.endsWith('/' + row.id)); + if (!row) return { status: 404, headers: {}, body: { error: { code: 'not_found' } } }; + if (r.method === 'DELETE') { rows.splice(rows.indexOf(row), 1); return { status: 200, headers: {}, body: { data: {} } }; } + return { status: 200, headers: {}, body: { data: row } }; + }, + }; + api.transport = api.request.bind(api); + return api; +} +module.exports = { auth, server }; diff --git a/tests/hooks.test.cjs b/tests/hooks.test.cjs index ab2626a..686c36f 100644 --- a/tests/hooks.test.cjs +++ b/tests/hooks.test.cjs @@ -4,37 +4,17 @@ const assert = require('node:assert/strict'); const hooks = require('../shared/hooks.cjs'); const Ajv = require('ajv'); const addFormats = require('ajv-formats'); const { events } = require('../shared/catalog.cjs'); -const auth = { apiKey: 'synthetic-project-token-not-a-credential', projectId: '11111111-1111-4111-8111-111111111111' }; +const { auth, server } = require('./fake-webhooks.cjs'); const url = 'https://example.com/synthetic-callback'; const key = 'synthetic-trigger-1'; -function server() { - const rows = []; const calls = []; const receipts = new Map(); let lost = false; - return { rows, calls, loseNextCreate() { lost = true; }, async request(r) { - calls.push(r); - assert.equal(new URL(r.url).origin, 'https://api.polymorfa.com'); - assert.equal(r.headers.Authorization, `Bearer ${auth.apiKey}`); - if (r.method === 'GET' && r.url.endsWith('/webhooks')) return { status: 200, body: { data: rows, page: { hasMore: false, nextCursor: null } } }; - if (r.method === 'POST') { - const requestKey = r.headers['Idempotency-Key']; - if (receipts.has(requestKey)) return receipts.get(requestKey); - const row = { ...r.body, owner: 'project', projectId: auth.projectId, id: `synthetic-hook-${receipts.size + 1}`, headers: r.body.headers.map(h => ({ name: h.name })) }; rows.push(row); - const receipt = { status: 200, body: { data: { webhook: row, secret: 'SYNTHETIC-ONE-TIME-SIGNING-SECRET' } } }; - receipts.set(requestKey, receipt); - if (lost) { lost = false; throw new Error('Response lost after accepted create'); } - return receipt; - } - const row = rows.find(row => r.url.endsWith('/' + row.id)); - if (!row) return { status: 404, body: { error: { code: 'not_found' } } }; - if (r.method === 'DELETE') { rows.splice(rows.indexOf(row), 1); return { status: 200, body: { data: {} } }; } - return { status: 200, body: { data: row } }; - } }; -} +const NONCE = '11111111-1111-4111-8111-111111111111'; +function memory() { let value; return { get: () => value, set: v => { value = v || undefined; }, peek: () => value }; } for (const group of Object.keys(events)) test(`${group}: activation, reactivation, authentication and owned cleanup`, async () => { const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, url, group, key, transport, 'test', 'registration-generation-1'); + const first = await hooks.subscribe(auth, url, group, key, transport, 'test', NONCE); assert.deepEqual(Object.keys(first).sort(), ['group', 'id', 'markerName', 'subscriptionKey', 'url']); assert.ok(!JSON.stringify(first).includes('SYNTHETIC-ONE-TIME-SIGNING-SECRET')); - assert.deepEqual(await hooks.subscribe(auth, url, group, key, transport, 'test', 'registration-generation-1'), first); + assert.deepEqual(await hooks.subscribe(auth, url, group, key, transport, 'test', NONCE), first); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); const marker = hooks.identity(auth, group, key); assert.ok(hooks.authenticate(auth, group, key, { [marker.name.toLowerCase()]: marker.value })); @@ -46,19 +26,19 @@ for (const group of Object.keys(events)) test(`${group}: activation, reactivatio }); test('an ambiguous accepted create is recovered without another create', async () => { const s = server(); s.loseNextCreate(); - const result = await hooks.subscribe(auth, url, 'messageReceived', key, s.request.bind(s), 'test', 'registration-generation-1'); + const result = await hooks.subscribe(auth, url, 'messageReceived', key, s.request.bind(s), 'test', NONCE); assert.ok(result.id); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('an operator-disabled hook is preserved instead of silently enabling or duplicating it', async () => { const s = server(); const t = s.request.bind(s); - await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); s.rows[0].enabled = false; - await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'), e => e.code === 'subscription_changed_requires_reconciliation'); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', NONCE); s.rows[0].enabled = false; + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', NONCE), e => e.code === 'subscription_changed_requires_reconciliation'); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('a different callback cannot reuse the same active subscription identity', async () => { const s = server(); const t = s.request.bind(s); - await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); - await assert.rejects(hooks.subscribe(auth, 'https://other.example/hook', 'messageReceived', key, t, 'test', 'registration-generation-1'), e => e.code === 'subscription_key_already_in_use'); + await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', NONCE); + await assert.rejects(hooks.subscribe(auth, 'https://other.example/hook', 'messageReceived', key, t, 'test', NONCE), e => e.code === 'subscription_key_already_in_use'); assert.equal(s.calls.filter(r => r.method === 'POST').length, 1); }); test('owned lookup follows cursor pages and refuses looping cursors', async () => { @@ -71,7 +51,7 @@ test('owned lookup follows cursor pages and refuses looping cursors', async () = await assert.rejects(hooks.find(auth, url, 'messageReceived', key, async () => ({ status: 200, body: { data: [], page: { hasMore: true, nextCursor: 'loop' } } }), 'test'), e => e.code === 'invalid_subscription_pagination'); }); test('cleanup refuses another subscription and never falls back to broader credentials', async () => { - const s = server(); const t = s.request.bind(s); const owned = await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', 'registration-generation-1'); + const s = server(); const t = s.request.bind(s); const owned = await hooks.subscribe(auth, url, 'messageReceived', key, t, 'test', NONCE); await assert.rejects(hooks.unsubscribe(auth, owned.id, 'https://another.example/hook', 'messageReceived', key, t, 'test'), e => e.code === 'subscription_ownership_mismatch'); assert.equal(s.calls.filter(r => r.method === 'DELETE').length, 0); }); @@ -100,30 +80,81 @@ test('public marker and create keys do not reveal receiver verifier material', ( assert.ok(!marker.name.includes(marker.value.slice(0, 24))); }); -test('saved creation generations prevent deleted-receipt activation and permit confirmed recreation', async () => { - const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-1'); - await hooks.unsubscribe(auth, first.id, url, 'messageReceived', key, transport, 'test'); - await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-1'), e => e.code === 'subscription_requires_reconciliation'); - assert.equal(s.rows.length, 0); - const second = await hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'registration-generation-2'); - const requests = s.calls.filter(r => r.method === 'POST'); - assert.equal(requests.length, 3); assert.equal(s.rows.length, 1); assert.notEqual(second.id, first.id); - assert.equal(requests[0].headers['Idempotency-Key'], requests[1].headers['Idempotency-Key']); - assert.notEqual(requests[0].headers['Idempotency-Key'], requests[2].headers['Idempotency-Key']); - assert.ok(requests.every(r => !r.headers['Idempotency-Key'].includes(hooks.identity(auth, 'messageReceived', key).value))); +test('deactivate then reactivate registers a new webhook with a new creation key', async () => { + const s = server(); const store = memory(); + const first = await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + assert.equal(store.peek(), undefined, 'a confirmed activation clears its pending nonce'); + await hooks.unsubscribe(auth, first.id, url, 'messageReceived', key, s.transport, 'test'); + const second = await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + assert.equal(s.rows.length, 1); assert.equal(s.rows[0].id, second.id); assert.notEqual(second.id, first.id); + const posts = s.posts(); assert.equal(posts.length, 2); + assert.notEqual(posts[0].headers['Idempotency-Key'], posts[1].headers['Idempotency-Key']); + assert.ok(posts.every(r => !r.headers['Idempotency-Key'].includes(hooks.identity(auth, 'messageReceived', key).value))); +}); + +test('a test callback followed by the production callback activates both in turn', async () => { + const s = server(); const store = memory(); + const test = await hooks.activate(auth, 'https://n8n.example/webhook-test/abc/webhook', 'messageReceived', key, s.transport, 'test', store); + await hooks.unsubscribe(auth, test.id, test.url, 'messageReceived', key, s.transport, 'test'); + const live = await hooks.activate(auth, 'https://n8n.example/webhook/abc/webhook', 'messageReceived', key, s.transport, 'test', store); + assert.equal(s.rows.length, 1); assert.equal(s.rows[0].url, live.url); +}); + +test('a lost create response followed by a retry of the same activation yields one webhook', async () => { + const s = server(); const store = memory(); + // The create is accepted, its response is lost and the inventory does not show it yet. + s.loseNextCreate(); s.hideNewest(2); + await assert.rejects(hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store), e => e.code === 'subscription_requires_reconciliation'); + const pending = store.peek(); assert.ok(pending && pending.nonce, 'the uncertain activation keeps its nonce'); + // The retry runs before the inventory catches up, so it sends the create again with the same key. + s.hideNewest(1); + const owned = await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + const posts = s.posts(); + assert.equal(posts.length, 2); assert.equal(posts[0].headers['Idempotency-Key'], posts[1].headers['Idempotency-Key']); + assert.equal(s.rows.length, 1); assert.equal(owned.id, s.rows[0].id); assert.equal(store.peek(), undefined); +}); + +test('a lost create response is reconciled from the inventory without a second create', async () => { + const s = server(); const store = memory(); s.loseNextCreate(); + const owned = await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + assert.equal(s.posts().length, 1); assert.equal(s.rows.length, 1); assert.equal(owned.id, s.rows[0].id); +}); + +test('a replayed receipt for a deleted webhook is refused, then the next activation registers a new one', async () => { + const s = server(); const store = memory(); + s.loseNextCreate(); s.hideNewest(2); + await assert.rejects(hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store), e => e.code === 'subscription_requires_reconciliation'); + const nonce = store.peek().nonce; + s.rows.splice(0); // Someone deletes the webhook before the retry. + await assert.rejects(hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store), e => e.code === 'subscription_receipt_deleted' && /Activate the trigger again/.test(e.message)); + assert.equal(s.rows.length, 0, 'the dead webhook is not reported as live and nothing new was created'); + assert.equal(s.posts()[1].headers['Idempotency-Key'], s.posts()[0].headers['Idempotency-Key']); + assert.equal(store.peek(), undefined, 'the spent nonce is discarded'); + const owned = await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + assert.equal(s.rows.length, 1); assert.equal(owned.id, s.rows[0].id); + assert.notEqual(s.posts()[2].headers['Idempotency-Key'], s.posts()[0].headers['Idempotency-Key']); + assert.ok(nonce); +}); + +test('a replayed receipt for a webhook that is live but missing from the inventory needs reconciliation', async () => { + const s = server(); s.loseNextCreate(); s.hideNewest(10); const store = memory(); + await assert.rejects(hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store), e => e.code === 'subscription_requires_reconciliation'); + await assert.rejects(hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store), e => e.code === 'subscription_receipt_requires_reconciliation'); + assert.equal(s.rows.length, 1); assert.ok(store.peek(), 'an unresolved activation keeps its nonce'); +}); + +test('creation keys require an adapter nonce and a pending nonce is bound to its callback and trigger', async () => { + assert.throws(() => hooks.creationKey('a'.repeat(64), 'short'), e => e.code === 'invalid_creation_nonce'); + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, async () => ({ status: 200, body: { data: [], page: {} } }), 'test'), e => e.code === 'invalid_creation_nonce'); + const s = server(); const store = memory(); + store.set({ nonce: NONCE, url: 'https://other.example/hook', group: 'messageReceived', subscriptionKey: key }); + await hooks.activate(auth, url, 'messageReceived', key, s.transport, 'test', store); + assert.notEqual(s.posts()[0].headers['Idempotency-Key'], hooks.creationKey(hooks.identity(auth, 'messageReceived', key).key, NONCE)); }); -test('uncertain creation retries use the same saved key and refuse a missing generation', async () => { - const keys = []; - const transport = async r => { - if (r.method === 'GET') return { status: 200, body: { data: [], page: { hasMore: false } } }; - keys.push(r.headers['Idempotency-Key']); throw new Error('Uncertain synthetic transport'); - }; - for (let i = 0; i < 2; i++) await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', 'saved-generation'), e => e.code === 'subscription_requires_reconciliation'); - assert.equal(keys.length, 2); assert.equal(keys[0], keys[1]); - await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test'), e => e.code === 'invalid_registration_key'); - assert.equal(keys.length, 2); +test('uncertain creation errors keep reconcile guidance even when the server sends an error code', async () => { + const transport = async r => r.method === 'GET' ? { status: 200, body: { data: [], page: { hasMore: false } } } : { status: 503, headers: {}, body: { error: { code: 'internal_error' } } }; + await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, transport, 'test', NONCE), e => e.code === 'subscription_requires_reconciliation'); }); test('public QuickLink reference survives the event projection without arbitrary transport data', () => { @@ -135,9 +166,9 @@ test('public QuickLink reference survives the event projection without arbitrary test('callback normalization preserves ownership across activation and cleanup', async () => { const s = server(); const transport = s.request.bind(s); - const first = await hooks.subscribe(auth, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test', 'registration-generation-1'); + const first = await hooks.subscribe(auth, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test', NONCE); assert.equal(s.rows[0].url, 'https://example.com/'); - assert.deepEqual(await hooks.subscribe(auth, 'https://example.com/', 'messageReceived', key, transport, 'test', 'registration-generation-1'), first); + assert.deepEqual(await hooks.subscribe(auth, 'https://example.com/', 'messageReceived', key, transport, 'test', NONCE), first); await hooks.unsubscribe(auth, first.id, 'https://EXAMPLE.COM:443', 'messageReceived', key, transport, 'test'); assert.equal(s.rows.length, 0); assert.equal(hooks.sameCallback(url + '?key=A', url + '?key=a'), false); @@ -148,6 +179,6 @@ for (const status of [403, 422, 429]) test(`definite subscription rejection ${st await assert.rejects(hooks.subscribe(auth, url, 'messageReceived', key, async r => { if (r.method === 'GET') { reads++; return { status: 200, body: { data: [], page: { hasMore: false } } }; } return { status, headers: { 'retry-after': '17', 'x-request-id': 'safe-request' }, body: { error: { code: 'scope_denied' } } }; - }, 'test', 'registration-generation-1'), e => e.status === status && e.code === 'scope_denied' && e.retryAfter === 17 && e.requestId === 'safe-request'); + }, 'test', NONCE), e => e.status === status && e.code === 'scope_denied' && e.retryAfter === 17 && e.requestId === 'safe-request'); assert.equal(reads, 1); }); diff --git a/tests/make.test.cjs b/tests/make.test.cjs index cf6638d..2381058 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -42,12 +42,13 @@ test('Make preserves an operator-disabled owned hook', () => { test('Make activation keys are independent of receiver secrets and change between activations', () => { const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); - const first = iml.polymorfaCreateKey(marker.key, '11111111-1111-4111-8111-111111111111'); - const second = iml.polymorfaCreateKey(marker.key, '22222222-2222-4222-8222-222222222222'); - assert.notEqual(first, second); assert.ok(!first.includes(marker.value)); - assert.equal(first, hooks.creationKey(marker.key, '11111111-1111-4111-8111-111111111111')); - assert.equal(first, iml.polymorfaCreateKey(marker.key, '11111111-1111-4111-8111-111111111111')); - assert.throws(() => iml.polymorfaCreateKey(marker.key, '')); + const n1 = iml.polymorfaCreationNonce('https://hook.make.com/synthetic'); const n2 = iml.polymorfaCreationNonce('https://hook.make.com/synthetic'); + assert.match(n1, /^[a-f0-9]{64}$/); assert.notEqual(n1, n2); + const first = iml.polymorfaCreateKey(marker.key, n1); + assert.notEqual(first, iml.polymorfaCreateKey(marker.key, n2)); assert.ok(!first.includes(marker.value)); + assert.equal(first, hooks.creationKey(marker.key, n1)); + assert.equal(first, iml.polymorfaCreateKey(marker.key, n1)); + for (const bad of ['', 'short', undefined, 'x'.repeat(129)]) assert.throws(() => iml.polymorfaCreateKey(marker.key, bad)); const base = JSON.parse(readFileSync('make/base.json')); for (const path of ['request.headers.idempotency-key', 'request.body.headers', 'response.body.data.idempotency.key']) assert.ok(base.log.sanitize.includes(path)); }); @@ -62,7 +63,9 @@ test('Make verifies live creation receipts and maps the optional public QuickLin const sample = { ...require('../fixtures/events.json').numberState, externalId: 'synthetic-order-reference', transportSecret: 'must-not-escape' }; assert.deepEqual(JSON.parse(JSON.stringify(iml.polymorfaEnvelope(sample, 'numberState'))), hooks.envelope(sample, 'numberState')); const attach = JSON.parse(readFileSync('make/webhooks/numberState/attach.json')); - assert.ok(attach[1].headers['Idempotency-Key'].includes('parameters.registrationKey')); + assert.ok(attach[0].response.temp.nonce.includes('polymorfaCreationNonce(webhook.url)')); + assert.ok(attach[1].headers['Idempotency-Key'].includes('temp.nonce')); + assert.ok(!JSON.stringify(attach).includes('registrationKey')); assert.ok(attach[2].response.data.externalHookId.includes('polymorfaLiveHook')); assert.equal(JSON.parse(readFileSync('make/modules/react/parameters.json')).find(p => p.name === 'reaction').required, false); }); diff --git a/tests/native.test.cjs b/tests/native.test.cjs index cf687c9..8c60f84 100644 --- a/tests/native.test.cjs +++ b/tests/native.test.cjs @@ -4,6 +4,7 @@ const { Polymorfa } = require('../n8n/dist/nodes/Polymorfa/Polymorfa.node.js'); const { PolymorfaTrigger } = require('../n8n/dist/nodes/Polymorfa/PolymorfaTrigger.node.js'); const zapier = require('../zapier'); const hooks = require('../shared/hooks.cjs'); +const fake = require('./fake-webhooks.cjs'); const auth = { apiKey: 'synthetic-project-token', projectId: '11111111-1111-4111-8111-111111111111' }; const input = { operation: 'sendText', session: '739182640518203', recipientType: 'id', recipient: '9007199254740993123', text: 'Synthetic', idempotencyKey: 'synthetic-write-1', writeCreatedAt: new Date().toISOString() }; test('compiled n8n node uses native request helpers, preserved input pairing and disabled redirects', async () => { @@ -89,5 +90,50 @@ test('Zapier editor permits removal and sends an explicit empty reaction when th let body; await zapier.creates.react.operation.perform({ request: async r => { body = r.body; return { status: 200, content: '{"data":{}}', headers: {} }; } }, { authData: auth, inputData: { ...input, messageId: '739182640518205' } }); assert.equal(body.reaction, ''); - for (const trigger of Object.values(zapier.triggers)) assert.equal(trigger.operation.inputFields.find(f => f.key === 'registrationKey').required, true); + for (const trigger of Object.values(zapier.triggers)) assert.deepEqual(trigger.operation.inputFields.map(f => f.key), ['subscriptionKey']); +}); + +function n8nHookContext(s, staticData, params) { + return { getCredentials: async () => fake.auth, getNodeWebhookUrl: () => params.url, getNodeParameter: k => params[k], getWorkflowStaticData: () => staticData, + helpers: { httpRequest: async r => { const res = await s.request({ method: r.method, url: r.url, headers: r.headers, query: r.qs, body: r.body }); return { statusCode: res.status, headers: res.headers, body: res.body }; } } }; +} +test('compiled n8n trigger survives test listen, activation, deactivation and reactivation', async () => { + const s = fake.server(); const staticData = {}; const methods = new PolymorfaTrigger().webhookMethods.default; + const params = { eventGroup: 'messageReceived', subscriptionKey: 'synthetic-trigger', url: 'https://n8n.example/webhook-test/abc/webhook' }; + const run = async () => { + const context = n8nHookContext(s, staticData, params); + if (!(await methods.checkExists.call(context))) await methods.create.call(context); + assert.equal(staticData.pendingCreation, undefined); assert.equal(s.rows.length, 1); assert.equal(staticData.subscription.id, s.rows[0].id); + await methods.delete.call(context); assert.equal(s.rows.length, 0); assert.equal(staticData.subscription, undefined); + }; + await run(); await run(); + params.url = 'https://n8n.example/webhook/abc/webhook'; + await run(); await run(); + assert.equal(s.posts().length, 4); assert.equal(new Set(s.posts().map(r => r.headers['Idempotency-Key'])).size, 4); + assert.deepEqual(new PolymorfaTrigger().description.properties.map(p => p.name), ['eventGroup', 'subscriptionKey', 'scopeNotice']); +}); +test('compiled n8n trigger keeps an uncertain activation nonce in node static data for its retry', async () => { + const s = fake.server(); const staticData = {}; const methods = new PolymorfaTrigger().webhookMethods.default; + const context = n8nHookContext(s, staticData, { eventGroup: 'messageReceived', subscriptionKey: 'synthetic-trigger', url: 'https://n8n.example/webhook/abc/webhook' }); + s.loseNextCreate(); s.hideNewest(2); + await assert.rejects(methods.create.call(context), e => e.code === 'subscription_requires_reconciliation'); + assert.ok(staticData.pendingCreation.nonce); s.hideNewest(1); + await methods.create.call(context); + assert.equal(s.rows.length, 1); assert.equal(s.posts()[0].headers['Idempotency-Key'], s.posts()[1].headers['Idempotency-Key']); + assert.equal(staticData.pendingCreation, undefined); +}); + +function zapierZ(s) { + return { request: async r => { const res = await s.request({ method: r.method, url: r.url, headers: r.headers, query: r.params, body: r.body }); return { status: res.status, headers: res.headers, content: JSON.stringify(res.body) }; }, errors: { Error } }; +} +test('Zapier REST hook turns off and on again with a fresh activation each time', async () => { + const s = fake.server(); const z = zapierZ(s); const operation = zapier.triggers.messageReceived.operation; + const bundle = () => ({ authData: { ...fake.auth }, inputData: { subscriptionKey: 'synthetic-trigger' }, targetUrl: 'https://hooks.zapier.com/hooks/standard/1/abc/' }); + for (let i = 0; i < 3; i++) { + const subscribeData = await operation.performSubscribe(z, bundle()); + assert.equal(s.rows.length, 1); assert.equal(subscribeData.id, s.rows[0].id); + assert.ok(!JSON.stringify(subscribeData).includes('SYNTHETIC-ONE-TIME-SIGNING-SECRET')); + await operation.performUnsubscribe(z, { ...bundle(), subscribeData }); assert.equal(s.rows.length, 0); + } + assert.equal(new Set(s.posts().map(r => r.headers['Idempotency-Key'])).size, 3); }); diff --git a/tests/pipedream.test.mjs b/tests/pipedream.test.mjs index 181af2a..aa2a033 100644 --- a/tests/pipedream.test.mjs +++ b/tests/pipedream.test.mjs @@ -18,3 +18,37 @@ test('Pipedream source rejects forgery and emits only the defined envelope with await source.run.call(context, { headers: { [marker.name]: marker.value }, body: sample }); assert.deepEqual(emitted[0].data, sample); assert.equal(emitted[0].metadata.id, sample.id); assert.equal(source.dedupe, 'unique'); }); + +import app from '../pipedream/polymorfa.app.mjs'; +import fake from './fake-webhooks.cjs'; +import { readdirSync } from 'node:fs'; +function pipedreamApp(s) { + return { ...app.methods, $auth: { api_key: fake.auth.apiKey, project_id: fake.auth.projectId }, transport: () => s.transport }; +} +function serviceDb() { const values = new Map(); return { values, get: k => values.get(k), set: (k, v) => values.set(k, v) }; } +test('Pipedream source deactivates and reactivates with a fresh creation nonce in $.service.db', async () => { + const s = fake.server(); const db = serviceDb(); + const context = { polymorfa: pipedreamApp(s), db, http: { endpoint: 'https://synthetic.m.pipedream.net' }, subscriptionKey: 'source-1' }; + for (let i = 0; i < 3; i++) { + await source.hooks.activate.call(context); + assert.equal(s.rows.length, 1); assert.equal(db.get('ownedSubscription').id, s.rows[0].id); assert.equal(db.get('pendingCreation'), null); + await source.hooks.deactivate.call(context); + assert.equal(s.rows.length, 0); assert.equal(db.get('ownedSubscription'), null); + } + assert.equal(new Set(s.posts().map(r => r.headers['Idempotency-Key'])).size, 3); +}); +test('Pipedream retries an uncertain activation with the nonce saved in $.service.db', async () => { + const s = fake.server(); const db = serviceDb(); + const context = { polymorfa: pipedreamApp(s), db, http: { endpoint: 'https://synthetic.m.pipedream.net' }, subscriptionKey: 'source-1' }; + s.loseNextCreate(); s.hideNewest(2); + await assert.rejects(source.hooks.activate.call(context), e => e.code === 'subscription_requires_reconciliation'); + assert.ok(db.get('pendingCreation').nonce); s.hideNewest(1); + await source.hooks.activate.call(context); + assert.equal(s.rows.length, 1); assert.equal(s.posts()[0].headers['Idempotency-Key'], s.posts()[1].headers['Idempotency-Key']); +}); +test('Pipedream sources expose no webhook creation key prop', async () => { + for (const name of readdirSync('pipedream/sources')) { + const { default: component } = await import(`../pipedream/sources/${name}/${name}.mjs`); + assert.deepEqual(Object.keys(component.props).sort(), ['db', 'http', 'polymorfa', 'subscriptionKey']); + } +}); diff --git a/zapier/index.js b/zapier/index.js index ee04b51..e72226c 100644 --- a/zapier/index.js +++ b/zapier/index.js @@ -36,13 +36,16 @@ const triggers = Object.fromEntries(Object.entries(events).map(([key, event]) => type: 'hook', canPaginate: false, inputFields: [ { key: 'subscriptionKey', label: 'Subscription Key', type: 'string', required: true, helpText: 'A unique stable key for this Zap trigger. Project-wide events are sent to Zapier. Use a different key for another trigger.' }, - { key: 'registrationKey', label: 'Webhook Creation Key', type: 'string', required: true, helpText: 'Save a unique key for this registration and reuse it for uncertain retries. After confirmed deletion, use a new key for the next registration.' }, ], async performSubscribe(z, bundle) { // Zapier's logger scrubs authData values before its HTTP logs run. // Register the derived receiver secret before the registration request. bundle.authData.webhookVerificationSecret = hooks.identity(bundle.authData, key, bundle.inputData.subscriptionKey).value; - const owned = await withThrottling(z, () => hooks.subscribe(bundle.authData, bundle.targetUrl, key, bundle.inputData.subscriptionKey, transport(z), 'zapier', bundle.inputData.registrationKey)); + // Zapier keeps no private state before performSubscribe returns, so each call is one activation + // with its own creation nonce. Its result is stored by Zapier as subscribeData. + let pending; + const store = { get: () => pending, set: value => { pending = value || undefined; } }; + const owned = await withThrottling(z, () => hooks.activate(bundle.authData, bundle.targetUrl, key, bundle.inputData.subscriptionKey, transport(z), 'zapier', store)); return { ...owned, url: bundle.targetUrl }; }, async performUnsubscribe(z, bundle) { diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs index 67b78c8..59522cc 100644 --- a/zapier/shared/core.cjs +++ b/zapier/shared/core.cjs @@ -7,7 +7,7 @@ const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/zapier/shared/hooks.cjs b/zapier/shared/hooks.cjs index ff5cf15..f15231e 100644 --- a/zapier/shared/hooks.cjs +++ b/zapier/shared/hooks.cjs @@ -1,7 +1,7 @@ // Generated from shared/. Run npm run generate. 'use strict'; -const { createHmac, timingSafeEqual } = require('node:crypto'); +const { createHmac, timingSafeEqual, randomUUID } = require('node:crypto'); const { events } = require('./catalog.cjs'); const { ORIGIN, headers, segment, required, parseResponse, IntegrationError } = require('./core.cjs'); function identity(auth, group, subscriptionKey) { @@ -20,10 +20,10 @@ function callback(url) { function sameCallback(a, b) { try { return callback(a) === callback(b); } catch { return false; } } -function creationKey(markerKey, registrationKey) { - required(registrationKey, 'registration_key'); - if (registrationKey.length > 200) throw new IntegrationError('invalid_registration_key'); - return 'integration-create-' + createHmac('sha256', markerKey).update(registrationKey).digest('hex'); +const NONCE = /^[A-Za-z0-9_-]{16,128}$/; +function creationKey(markerKey, nonce) { + if (typeof nonce !== 'string' || !NONCE.test(nonce)) throw new IntegrationError('invalid_creation_nonce'); + return 'integration-create-' + createHmac('sha256', markerKey).update(nonce).digest('hex'); } function owns(hook, auth, url, group, subscriptionKey, recordedMarker) { const marker = (recordedMarker || identity(auth, group, subscriptionKey).name).toLowerCase(); @@ -59,22 +59,18 @@ async function find(auth, url, group, subscriptionKey, transport, provider) { } throw new IntegrationError('subscription_inventory_requires_reconciliation'); } -async function subscribe(auth, url, group, subscriptionKey, transport, provider, registrationKey) { +async function subscribe(auth, url, group, subscriptionKey, transport, provider, nonce) { url = callback(url); const marker = identity(auth, group, subscriptionKey); - const createKey = creationKey(marker.key, registrationKey); + const createKey = creationKey(marker.key, nonce); const record = hook => ({ id: hook.id, url, group, subscriptionKey, markerName: marker.name }); const existing = await find(auth, url, group, subscriptionKey, transport, provider); if (existing) return record(existing); // Only the owned ID leaves this helper. The API's one-time signing secret is never emitted. + let r; try { - // Saved trigger configuration owns this generation across uncertain retries. - const r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); - if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); - // An idempotency receipt may outlive its deleted webhook. Verify live ownership. - const live = await find(auth, url, group, subscriptionKey, transport, provider); - if (!live || live.id !== r.data.webhook.id) throw new IntegrationError('subscription_receipt_requires_reconciliation'); - return record(live); + // The adapter keeps this activation's nonce in private state, so an uncertain retry reuses the key. + r = await call(auth, transport, provider, 'POST', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks`, { url, eventTypes: events[group].types, format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] }, createKey); } catch (error) { if (error instanceof IntegrationError && error.status >= 400 && error.status < 500) throw error; // A lost response may follow a successful create. Reconcile; never blindly create again. @@ -82,6 +78,37 @@ async function subscribe(auth, url, group, subscriptionKey, transport, provider, if (recovered) return record(recovered); throw new IntegrationError('subscription_requires_reconciliation'); } + if (!owns(r.data?.webhook, auth, url, group, subscriptionKey)) throw new IntegrationError('invalid_subscription_receipt'); + // An idempotency receipt may outlive its deleted webhook. Verify live ownership. + const live = await find(auth, url, group, subscriptionKey, transport, provider); + if (live && live.id === r.data.webhook.id) return record(live); + if (!live) { + try { await call(auth, transport, provider, 'GET', `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(r.data.webhook.id, 'webhook_id')}`); } + catch (error) { if (error.status === 404) throw new IntegrationError('subscription_receipt_deleted'); } + } + throw new IntegrationError('subscription_receipt_requires_reconciliation'); +} +function creation(url, group, subscriptionKey) { + return { nonce: randomUUID(), url: callback(url), group, subscriptionKey }; +} +// Store: provider-private { get(), set(value) } for this trigger's pending creation. +async function activate(auth, url, group, subscriptionKey, transport, provider, store) { + const target = callback(url); + let pending = await store.get(); + if (!pending || pending.url !== target || pending.group !== group || pending.subscriptionKey !== subscriptionKey || typeof pending.nonce !== 'string' || !NONCE.test(pending.nonce)) { + pending = creation(target, group, subscriptionKey); + } + // Save before the create so an uncertain retry of this activation sends the same key. + await store.set(pending); + let owned; + try { owned = await subscribe(auth, target, group, subscriptionKey, transport, provider, pending.nonce); } + catch (error) { + // The receipt names a confirmed-deleted webhook. The next activation needs a new nonce. + if (error.code === 'subscription_receipt_deleted') await store.set(null); + throw error; + } + await store.set(null); + return owned; } async function unsubscribe(auth, id, url, group, subscriptionKey, transport, provider, recordedMarker) { const path = `/platform/projects/${segment(auth.projectId, 'project_id')}/webhooks/${segment(id, 'webhook_id')}`; @@ -92,9 +119,10 @@ async function unsubscribe(auth, id, url, group, subscriptionKey, transport, pro if (!owns({ ...hook, enabled: true }, auth, url, group, subscriptionKey, recordedMarker)) throw new IntegrationError('subscription_ownership_mismatch'); await call(auth, transport, provider, 'DELETE', path, undefined, `integration-delete-${id}`); } -function authenticate(auth, group, subscriptionKey, incomingHeaders) { +function authenticate(auth, group, subscriptionKey, incomingHeaders, prefixes = ['']) { const expected = identity(auth, group, subscriptionKey); - const entries = Object.entries(incomingHeaders || {}).filter(([k]) => k.toLowerCase() === expected.name.toLowerCase()); + const names = prefixes.map(p => (p + expected.name).toLowerCase()); + const entries = Object.entries(incomingHeaders || {}).filter(([k]) => names.includes(k.toLowerCase())); if (entries.length !== 1 || typeof entries[0][1] !== 'string' || !/^[a-f0-9]{64}$/.test(entries[0][1])) return false; return timingSafeEqual(Buffer.from(entries[0][1]), Buffer.from(expected.value)); } @@ -103,4 +131,4 @@ function envelope(body, group) { // Copy the defined envelope only. Never export arbitrary top-level transport fields. return { id: body.id, session: body.session, ...(typeof body.externalId === 'string' ? { externalId: body.externalId } : {}), timestamp: body.timestamp, event: body.event, payload: body.payload }; } -module.exports = { identity, callback, sameCallback, creationKey, owns, find, subscribe, unsubscribe, authenticate, envelope }; +module.exports = { identity, callback, sameCallback, creationKey, creation, owns, find, subscribe, activate, unsubscribe, authenticate, envelope }; From c403f63fd07ecac915d65de8080b9e13aa14b01d Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:39:51 +0200 Subject: [PATCH 11/14] Make: match unusual hook URLs like shared code and detach by webhook ID Attach threw on any unrelated project webhook whose URL had userinfo, a fragment or a non-https scheme. Hook matching now treats an unparseable URL as no match, as shared sameCallback does, and a test runs shared find and the Make function over the same inventories. Detach now reads the recorded webhook with the single-webhook GET and treats 404 as already deleted, so cleanup works in projects with more than 100 webhooks. A new test runs the attach and detach steps through repeated off/on cycles. --- CHANGELOG.md | 2 + MAINTAINING.md | 7 ++- make/functions/polymorfaDetachHook.js | 11 ++-- make/functions/polymorfaFindHook.js | 3 +- make/functions/polymorfaOwnedHook.js | 3 +- make/webhooks/callEnded/detach.json | 8 ++- make/webhooks/messageReceived/detach.json | 8 ++- make/webhooks/messageState/detach.json | 8 ++- make/webhooks/numberState/detach.json | 8 ++- scripts/generate.mjs | 9 ++-- tests/make.test.cjs | 63 ++++++++++++++++++++--- 11 files changed, 89 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b9217..99c7d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Remove the Webhook Creation Key field from all triggers. Each activation uses its own adapter-generated nonce, so off/on, n8n test listening and Pipedream redeploys work, and a receipt for a deleted webhook is refused. +- Make: detach reads the recorded webhook by ID; unusual URLs on unrelated + project webhooks no longer break attach. - Preserve definite subscription errors and non-JSON uncertain outcomes; normalize callback ownership and native Zapier response headers. - Keep successful n8n batch items when Continue On Fail is enabled. Require diff --git a/MAINTAINING.md b/MAINTAINING.md index abdafc1..d619e99 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -91,5 +91,8 @@ cleanup during acceptance, including failed cleanup and credential rotation. Make uses provider-native custom IML functions, which require enablement by Make. The generated JavaScript functions are tested, but their IML bindings, private webhook-state mapping and attach/detach behavior require real platform acceptance. -Its hook preflight refuses inventories larger than one page instead of risking -a duplicate. Qualify pagination in Make before approving large-project triggers. +Its attach preflight refuses inventories larger than one page instead of risking +a duplicate. Detach reads the recorded webhook by ID, so cleanup works in any +project size. Qualify attach pagination in Make before approving large-project +triggers. Its hook matching treats an unparseable URL as no match, as the shared +code does; a test runs both over the same inventories. diff --git a/make/functions/polymorfaDetachHook.js b/make/functions/polymorfaDetachHook.js index c3b738a..f82fdf1 100644 --- a/make/functions/polymorfaDetachHook.js +++ b/make/functions/polymorfaDetachHook.js @@ -1,8 +1,7 @@ -function polymorfaDetachHook(body, id, projectId, ownedUrl, markerName, group) { +function polymorfaDetachHook(status, body, id, projectId, ownedUrl, markerName, group) { -if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.'); -const matches = body.data.filter(h => h.id === id); -if (matches.length > 1) throw new Error('Duplicate subscription IDs need reconciliation.'); -if (!matches.length) return false; -return iml.polymorfaOwnedHook({ data: matches[0] }, projectId, ownedUrl, markerName, group); +if (status === 404) return false; +if (status !== 200) throw new Error(iml.polymorfaError(status, {}, body)); +if (!body || !body.data || body.data.id !== id) throw new Error('Subscription ownership mismatch.'); +return iml.polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group); } diff --git a/make/functions/polymorfaFindHook.js b/make/functions/polymorfaFindHook.js index f1d15a2..40b208a 100644 --- a/make/functions/polymorfaFindHook.js +++ b/make/functions/polymorfaFindHook.js @@ -1,7 +1,8 @@ function polymorfaFindHook(body, auth, url, group, key) { const expected = iml.polymorfaHook(auth, group, key); const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; -const same = h => iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url); +// Match shared sameCallback: an invalid URL on any hook is simply not a match. +const same = h => { try { return iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url); } catch (error) { return false; } }; if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.'); if (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.'); const owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t)); diff --git a/make/functions/polymorfaOwnedHook.js b/make/functions/polymorfaOwnedHook.js index 39afa53..f0f63df 100644 --- a/make/functions/polymorfaOwnedHook.js +++ b/make/functions/polymorfaOwnedHook.js @@ -1,6 +1,7 @@ function polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group) { const types = {"messageReceived":["message.received"],"messageState":["message.sent","message.ack","message.failed","message.update"],"numberState":["session.status","session.connected","session.logged_out"],"callEnded":["call.ended"]}; +const same = (a, b) => { try { return iml.polymorfaCallback(a) === iml.polymorfaCallback(b); } catch (error) { return false; } }; const h = body.data; -if (!h || h.owner !== 'project' || h.projectId !== projectId || iml.polymorfaCallback(h.url) !== iml.polymorfaCallback(ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.'); +if (!h || h.owner !== 'project' || h.projectId !== projectId || !same(h.url, ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.'); return true; } diff --git a/make/webhooks/callEnded/detach.json b/make/webhooks/callEnded/detach.json index 433fe04..7915df1 100644 --- a/make/webhooks/callEnded/detach.json +++ b/make/webhooks/callEnded/detach.json @@ -1,13 +1,11 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "GET", - "qs": { - "limit": 100 - }, "response": { + "valid": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded') != null}}", "temp": { - "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded')}}" + "ownedPresent": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'callEnded')}}" } } }, diff --git a/make/webhooks/messageReceived/detach.json b/make/webhooks/messageReceived/detach.json index 8fbd3f5..49fd9cc 100644 --- a/make/webhooks/messageReceived/detach.json +++ b/make/webhooks/messageReceived/detach.json @@ -1,13 +1,11 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "GET", - "qs": { - "limit": 100 - }, "response": { + "valid": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived') != null}}", "temp": { - "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived')}}" + "ownedPresent": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageReceived')}}" } } }, diff --git a/make/webhooks/messageState/detach.json b/make/webhooks/messageState/detach.json index 016eb17..5fa841e 100644 --- a/make/webhooks/messageState/detach.json +++ b/make/webhooks/messageState/detach.json @@ -1,13 +1,11 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "GET", - "qs": { - "limit": 100 - }, "response": { + "valid": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState') != null}}", "temp": { - "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState')}}" + "ownedPresent": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'messageState')}}" } } }, diff --git a/make/webhooks/numberState/detach.json b/make/webhooks/numberState/detach.json index 3e46021..4f2c35b 100644 --- a/make/webhooks/numberState/detach.json +++ b/make/webhooks/numberState/detach.json @@ -1,13 +1,11 @@ [ { - "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks", + "url": "/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}", "method": "GET", - "qs": { - "limit": 100 - }, "response": { + "valid": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState') != null}}", "temp": { - "ownedPresent": "{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState')}}" + "ownedPresent": "{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; 'numberState')}}" } } }, diff --git a/scripts/generate.mjs b/scripts/generate.mjs index 599befe..93b36bf 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -72,9 +72,10 @@ imlFunction('polymorfaLiveHook', 'body, auth, url, group, key, expectedId', '', imlFunction('polymorfaCallback', 'value', '', `if (typeof value !== 'string' || /[\\s\\\\\\x00-\\x1f\\x7f]/.test(value)) throw new Error('Invalid callback.');\nconst parts = value.match(/^https:\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?$/i);\nif (!parts || parts[1].includes('@')) throw new Error('Invalid callback.');\nreturn 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || '');`); imlFunction('polymorfaError', 'status, headers, body', '', `try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; }`); imlFunction('polymorfaReceive', 'expectedName, expectedValue, group, headers, body', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase());\nif (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false;\nreturn !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload));`); -imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\nconst same = h => iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url);`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (same(h) || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); -imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || iml.polymorfaCallback(h.url) !== iml.polymorfaCallback(ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); -imlFunction('polymorfaDetachHook', 'body, id, projectId, ownedUrl, markerName, group', '', `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nconst matches = body.data.filter(h => h.id === id);\nif (matches.length > 1) throw new Error('Duplicate subscription IDs need reconciliation.');\nif (!matches.length) return false;\nreturn iml.polymorfaOwnedHook({ data: matches[0] }, projectId, ownedUrl, markerName, group);`); +imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\n// Match shared sameCallback: an invalid URL on any hook is simply not a match.\nconst same = h => { try { return iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url); } catch (error) { return false; } };`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (same(h) || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); +imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\nconst same = (a, b) => { try { return iml.polymorfaCallback(a) === iml.polymorfaCallback(b); } catch (error) { return false; } };`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || !same(h.url, ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); +// Detach reads the one recorded webhook by ID. 404 means it is already gone. +imlFunction('polymorfaDetachHook', 'status, body, id, projectId, ownedUrl, markerName, group', '', `if (status === 404) return false;\nif (status !== 200) throw new Error(iml.polymorfaError(status, {}, body));\nif (!body || !body.data || body.data.id !== id) throw new Error('Subscription ownership mismatch.');\nreturn iml.polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group);`); emit('make/app.json', { name: 'polymorfa', label: 'Polymorfa', version: 1, theme: '#7657ff', language: 'en', description: 'Send messages and receive project events with Polymorfa.' }); const makeError = '{{polymorfaError(statusCode; headers; body)}}'; @@ -118,7 +119,7 @@ for (const [key, e] of Object.entries(events)) { { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { data: { externalHookId: `{{polymorfaLiveHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}`, ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, ]); emit(`make/webhooks/${key}/detach.json`, [ - { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedPresent: `{{polymorfaDetachHook(body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}')}}` } } }, + { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'GET', response: { valid: `{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}') != null}}`, temp: { ownedPresent: `{{polymorfaDetachHook(statusCode; body; webhook.externalHookId; connection.projectId; webhook.ownedUrl; webhook.receiverName; '${key}')}}` } } }, { condition: '{{temp.ownedPresent}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks/{{polymorfaSegment(webhook.externalHookId)}}', method: 'DELETE', headers: { 'Idempotency-Key': 'integration-delete-{{webhook.externalHookId}}' } }, ]); const valid = `polymorfaReceive(parameters.receiverName; parameters.receiverValue; '${key}'; headers; body)`; diff --git a/tests/make.test.cjs b/tests/make.test.cjs index 2381058..10e1d28 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -70,16 +70,65 @@ test('Make verifies live creation receipts and maps the optional public QuickLin assert.equal(JSON.parse(readFileSync('make/modules/react/parameters.json')).find(p => p.name === 'reaction').required, false); }); -test('Make detach only deletes owned IDs in a complete successful inventory', () => { +test('Make detach reads the recorded webhook by ID and deletes it only when owned', () => { const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); const row = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://EXAMPLE.COM:443', format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; const args = ['owned', auth.projectId, 'https://example.com/', marker.name, 'messageReceived']; - assert.equal(iml.polymorfaDetachHook({ data: [], page: { hasMore: false } }, ...args), false); - assert.equal(iml.polymorfaDetachHook({ data: [row], page: { hasMore: false } }, ...args), true); - assert.throws(() => iml.polymorfaDetachHook({ data: [], page: { hasMore: true } }, ...args)); - assert.throws(() => iml.polymorfaDetachHook({ data: [{ ...row, projectId: 'other-project' }], page: { hasMore: false } }, ...args)); - const detach = JSON.parse(readFileSync('make/webhooks/messageReceived/detach.json')); - assert.ok(detach[0].url.endsWith('/webhooks')); assert.equal(detach[1].condition, '{{temp.ownedPresent}}'); + assert.equal(iml.polymorfaDetachHook(404, { error: { code: 'not_found' } }, ...args), false); + assert.equal(iml.polymorfaDetachHook(200, { data: row }, ...args), true); + assert.throws(() => iml.polymorfaDetachHook(200, { data: { ...row, id: 'different' } }, ...args)); + assert.throws(() => iml.polymorfaDetachHook(200, { data: { ...row, projectId: 'other-project' } }, ...args)); + assert.throws(() => iml.polymorfaDetachHook(200, { data: { ...row, url: 'https://user:pw@example.com/' } }, ...args), /ownership mismatch/); + assert.throws(() => iml.polymorfaDetachHook(503, { error: { code: 'unavailable' } }, ...args), /unavailable/); + for (const name of readdirSync('make/webhooks')) { + const detach = JSON.parse(readFileSync(`make/webhooks/${name}/detach.json`)); + assert.equal(detach[0].method, 'GET'); assert.ok(detach[0].url.endsWith('/webhooks/{{polymorfaSegment(webhook.externalHookId)}}')); + assert.equal(detach[0].qs, undefined); assert.ok(detach[0].response.valid.includes('polymorfaDetachHook(statusCode; body;')); + assert.equal(detach[1].condition, '{{temp.ownedPresent}}'); assert.equal(detach[1].method, 'DELETE'); + assert.deepEqual(JSON.parse(readFileSync(`make/webhooks/${name}/parameters.json`)).map(p => p.name), ['subscriptionKey']); + } +}); + +test('Make hook lookup matches shared find over the same inventories, including unusual URLs', async () => { + const marker = hooks.identity(auth, 'messageReceived', 'key'); + const own = { id: 'owned', owner: 'project', projectId: auth.projectId, url: 'https://example.com/', enabled: true, format: 'native', headers: [{ name: marker.name }], eventTypes: ['message.received'] }; + const other = url => ({ id: 'other-' + url, owner: 'project', projectId: auth.projectId, url, enabled: true, format: 'native', headers: [{ name: 'X-Other' }], eventTypes: ['message.received'] }); + const unusual = ['https://user:pw@crm.example.com/hook', 'https://crm.example.com/hook#frag', 'http://crm.example.com/hook', 'https://crm.example.com/a b', 'not a url', '', 'https://crm.example.com/\\x']; + const inventories = [[], [own], ...unusual.map(u => [other(u)]), ...unusual.map(u => [other(u), own]), [own, { ...own, id: 'dup' }], [{ ...own, enabled: false }], [{ ...own, url: 'https://elsewhere.example/' }]]; + for (const data of inventories) { + let core, make; + try { core = { id: (await hooks.find(auth, own.url, 'messageReceived', 'key', async () => ({ status: 200, body: { data, page: { hasMore: false } } }), 'test'))?.id || null }; } catch (e) { core = { error: true }; } + try { make = { id: iml.polymorfaFindHook({ data, page: { hasMore: false } }, auth, own.url, 'messageReceived', 'key') }; } catch (e) { make = { error: true }; } + assert.deepEqual(make, core, JSON.stringify(data.map(h => h.url))); + } +}); + +test('Make attach and detach steps turn a webhook off and on again without a duplicate', async () => { + const { server } = require('./fake-webhooks.cjs'); const s = server(auth); + const hookUrl = 'https://hook.eu1.make.com/synthetic'; + const base = 'https://api.polymorfa.com/platform/projects/' + auth.projectId + '/webhooks'; + const headers = extra => ({ Authorization: 'Bearer ' + auth.apiKey, ...extra }); + const attach = async () => { + const list = (await s.request({ method: 'GET', url: base, headers: headers() })).body; + const temp = { ownedId: iml.polymorfaFindHook(list, auth, hookUrl, 'messageReceived', 'key'), nonce: iml.polymorfaCreationNonce(hookUrl) }; + const marker = iml.polymorfaHook(auth, 'messageReceived', 'key'); + if (!temp.ownedId) { + const created = await s.request({ method: 'POST', url: base, headers: headers({ 'Idempotency-Key': iml.polymorfaCreateKey(marker.key, temp.nonce) }), body: { url: hookUrl, eventTypes: ['message.received'], format: 'native', enabled: true, headers: [{ name: marker.name, value: marker.value }] } }); + temp.createdId = created.body.data.webhook.id; + } + const live = (await s.request({ method: 'GET', url: base, headers: headers() })).body; + return { externalHookId: iml.polymorfaLiveHook(live, auth, hookUrl, 'messageReceived', 'key', temp.ownedId || temp.createdId), ownedUrl: hookUrl, receiverName: marker.name }; + }; + const detach = async webhook => { + const r = await s.request({ method: 'GET', url: base + '/' + webhook.externalHookId, headers: headers() }); + if (iml.polymorfaDetachHook(r.status, r.body, webhook.externalHookId, auth.projectId, webhook.ownedUrl, webhook.receiverName, 'messageReceived')) await s.request({ method: 'DELETE', url: base + '/' + webhook.externalHookId, headers: headers() }); + }; + for (let i = 0; i < 3; i++) { + const webhook = await attach(); assert.equal(s.rows.length, 1); assert.equal(webhook.externalHookId, s.rows[0].id); + await detach(webhook); assert.equal(s.rows.length, 0); + await detach(webhook); + } + assert.equal(new Set(s.posts().map(r => r.headers['Idempotency-Key'])).size, 3); }); test('Make non-2xx error handlers retain recovery and rate-limit guidance without response secrets', () => { From 20b353455611fa9ef8ed3cb3bc8f07f50f03cb1d Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:40:09 +0200 Subject: [PATCH 12/14] Zapier: read hook headers in the documented Http- prefixed form Zapier's bundle documentation says bundle.rawRequest headers, other than Content-Type and Content-Length, arrive in Camel-Case with an Http- prefix (X-Time-GMT becomes Http-X-Time-Gmt). The receiver read the plain marker name from cleanedRequest, so it could reject every real delivery. It now reads rawRequest headers and its raw JSON body, accepts the marker under either name exactly once, and falls back to cleanedRequest when rawRequest is absent. --- CHANGELOG.md | 1 + tests/native.test.cjs | 13 +++++++++++++ zapier/README.md | 5 +++++ zapier/index.js | 11 +++++++++-- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c7d1d..cdd7ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ redeploys work, and a receipt for a deleted webhook is refused. - Make: detach reads the recorded webhook by ID; unusual URLs on unrelated project webhooks no longer break attach. +- Zapier: accept the documented `Http-` prefixed raw request header form. - Preserve definite subscription errors and non-JSON uncertain outcomes; normalize callback ownership and native Zapier response headers. - Keep successful n8n batch items when Continue On Fail is enabled. Require diff --git a/tests/native.test.cjs b/tests/native.test.cjs index 8c60f84..f96e4a7 100644 --- a/tests/native.test.cjs +++ b/tests/native.test.cjs @@ -137,3 +137,16 @@ test('Zapier REST hook turns off and on again with a fresh activation each time' } assert.equal(new Set(s.posts().map(r => r.headers['Idempotency-Key'])).size, 3); }); +test('Zapier accepts the documented rawRequest shape with Http- prefixed Camel-Case headers', async () => { + const marker = hooks.identity(auth, 'messageReceived', 'synthetic-trigger'); const sample = require('../fixtures/events.json').messageReceived; + const operation = zapier.triggers.messageReceived.operation; const z = { errors: { Error } }; + // Zapier docs (Bundle object, bundle.rawRequest): headers other than Content-Length and Content-Type + // are prefixed with Http- and written in Camel-Case, for example X-Time-GMT becomes Http-X-Time-Gmt. + const camel = ('Http-' + marker.name).split('-').map(w => w[0].toUpperCase() + w.slice(1).toLowerCase()).join('-'); + const rawRequest = { method: 'POST', querystring: '', headers: { 'Content-Type': 'application/json', [camel]: marker.value, 'Http-User-Agent': 'polymorfa-webhooks' }, content: JSON.stringify(sample) }; + const bundle = { authData: auth, inputData: { subscriptionKey: 'synthetic-trigger' }, rawRequest, cleanedRequest: sample }; + assert.deepEqual(await operation.perform(z, bundle), [sample]); + await assert.rejects(operation.perform(z, { ...bundle, rawRequest: { ...rawRequest, headers: { 'Content-Type': 'application/json' } } })); + await assert.rejects(operation.perform(z, { ...bundle, rawRequest: { ...rawRequest, headers: { [camel]: marker.value, [marker.name]: marker.value } } }), 'one marker header only'); + await assert.rejects(operation.perform(z, { ...bundle, rawRequest: { ...rawRequest, headers: { [camel]: '0'.repeat(64) } } })); +}); diff --git a/zapier/README.md b/zapier/README.md index f957d32..ea6b925 100644 --- a/zapier/README.md +++ b/zapier/README.md @@ -19,6 +19,11 @@ samples, delivery, dedupe, cleanup, rotation and provider log redaction before requesting public review. Provider acceptance of the synthetic sample path is still required. +The receiver reads `bundle.rawRequest.headers`. Zapier's bundle documentation +says those headers, other than Content-Type and Content-Length, arrive with an +`Http-` prefix in Camel-Case. The receiver accepts that form or the plain name, +exactly once. Confirm it on a real REST Hook delivery before review. + Validate without contacting Zapier's style service: ```sh diff --git a/zapier/index.js b/zapier/index.js index e72226c..10c9ce0 100644 --- a/zapier/index.js +++ b/zapier/index.js @@ -53,8 +53,15 @@ const triggers = Object.fromEntries(Object.entries(events).map(([key, event]) => await withThrottling(z, () => hooks.unsubscribe(bundle.authData, owned.id, owned.url, owned.group, owned.subscriptionKey, transport(z), 'zapier', owned.markerName)); return {}; }, async perform(z, bundle) { - if (!hooks.authenticate(bundle.authData, key, bundle.inputData.subscriptionKey, bundle.cleanedRequest.headers)) throw new z.errors.Error('Webhook authentication failed.', 'WebhookAuthentication', 401); - return [hooks.envelope(bundle.cleanedRequest.content, key)]; + // Zapier documents bundle.rawRequest headers as Camel-Case with an Http- prefix + // (except Content-Type and Content-Length). Accept that form and the plain name, once. + const incoming = bundle.rawRequest?.headers || bundle.cleanedRequest?.headers; + if (!hooks.authenticate(bundle.authData, key, bundle.inputData.subscriptionKey, incoming, ['', 'http-'])) throw new z.errors.Error('Webhook authentication failed.', 'WebhookAuthentication', 401); + let body = bundle.cleanedRequest?.content; + if (typeof bundle.rawRequest?.content === 'string') { + try { body = JSON.parse(bundle.rawRequest.content); } catch { throw new z.errors.Error('Invalid webhook body.', 'InvalidEvent', 400); } + } + return [hooks.envelope(body, key)]; }, async performList() { return [require('./shared/samples.json')[key]]; }, sample: require('./shared/samples.json')[key], From b8bc1dbb58ec658944593f2a530d91ee4a246edb Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:40:31 +0200 Subject: [PATCH 13/14] Let the n8n inbound handoff recipe activate as imported The trigger had an empty subscription key, so activation failed after import. It now derives the key from the workflow ID, so each imported copy has its own. A recipe test checks the trigger parameters against the node description and activates the trigger. --- CHANGELOG.md | 1 + recipes/README.md | 5 +++-- recipes/n8n/inbound-handoff.json | 2 +- tests/recipes.test.cjs | 21 +++++++++++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd7ea7..63d4bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Make: detach reads the recorded webhook by ID; unusual URLs on unrelated project webhooks no longer break attach. - Zapier: accept the documented `Http-` prefixed raw request header form. +- The n8n inbound handoff recipe derives its subscription key from the workflow ID. - Preserve definite subscription errors and non-JSON uncertain outcomes; normalize callback ownership and native Zapier response headers. - Keep successful n8n batch items when Continue On Fail is enabled. Require diff --git a/recipes/README.md b/recipes/README.md index 2187505..4148763 100644 --- a/recipes/README.md +++ b/recipes/README.md @@ -28,8 +28,9 @@ Save `notificationStartedAt` when that logical notification first begins. Do not replace it with the current clock on every attempt. Missing inputs stop before a send. For a real send, configure a synthetic test recipient you control. -The inbound workflow needs its own subscription key, Polymorfa credential and -authorized destination. Its destination starts at `example.invalid`; configure +The inbound trigger derives its subscription key from the workflow ID, so each +imported copy has its own key. Select a Polymorfa credential and the trigger can +activate as imported. Configure the authorized destination before relying on it. Its destination starts at `example.invalid`; configure destination authentication before activation. It sends project-wide message content to the selected external system. Add persistent dedupe by `id` before effects. Neither workflow is activated by importing it. diff --git a/recipes/n8n/inbound-handoff.json b/recipes/n8n/inbound-handoff.json index 111acf6..3c6ff5f 100644 --- a/recipes/n8n/inbound-handoff.json +++ b/recipes/n8n/inbound-handoff.json @@ -2,7 +2,7 @@ "name": "Polymorfa inbound handoff (configure before use)", "active": false, "nodes": [ - { "id": "inbound-message", "name": "Receive project messages", "type": "n8n-nodes-polymorfa.polymorfaTrigger", "typeVersion": 1, "position": [0, 0], "parameters": { "eventGroup": "messageReceived", "subscriptionKey": "" } }, + { "id": "inbound-message", "name": "Receive project messages", "type": "n8n-nodes-polymorfa.polymorfaTrigger", "typeVersion": 1, "position": [0, 0], "parameters": { "eventGroup": "messageReceived", "subscriptionKey": "={{ 'inbound-handoff-' + $workflow.id }}" } }, { "id": "authorized-destination", "name": "Configure destination auth and persistent dedupe", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [260, 0], "parameters": { "method": "POST", "url": "https://example.invalid/configure-authorized-destination", "sendBody": true, "specifyBody": "json", "jsonBody": "={{JSON.stringify($json)}}", "options": {} } } ], "connections": { "Receive project messages": { "main": [[{ "node": "Configure destination auth and persistent dedupe", "type": "main", "index": 0 }]] } }, diff --git a/tests/recipes.test.cjs b/tests/recipes.test.cjs index 594c3ce..cb6f8d0 100644 --- a/tests/recipes.test.cjs +++ b/tests/recipes.test.cjs @@ -12,3 +12,24 @@ test('order notification rejects an absent business identity before the send nod for (const notificationId of [undefined, null, '', ' ', 0]) assert.throws(() => run({ notificationId })); assert.equal(run({ notificationId: 'saved-order-1' })[0].json.notificationId, 'saved-order-1'); }); + +const { PolymorfaTrigger } = require('../n8n/dist/nodes/Polymorfa/PolymorfaTrigger.node.js'); +const fake = require('./fake-webhooks.cjs'); +test('inbound handoff trigger parameters satisfy the node description and activate as imported', async () => { + const inbound = require('../recipes/n8n/inbound-handoff.json'); + const node = inbound.nodes.find(n => n.type === 'n8n-nodes-polymorfa.polymorfaTrigger'); + const description = new PolymorfaTrigger().description; + const names = description.properties.map(p => p.name); + for (const name of Object.keys(node.parameters)) assert.ok(names.includes(name), `unknown parameter ${name}`); + for (const p of description.properties.filter(p => p.required)) assert.ok(typeof node.parameters[p.name] === 'string' && node.parameters[p.name].trim(), `missing ${p.name}`); + // Evaluate the recipe's n8n expression the way n8n resolves it for this workflow. + const evaluate = value => value.startsWith('={{') ? vm.runInNewContext(value.slice(3, -2), { $workflow: { id: 'synthetic-workflow-17' } }) : value; + const params = Object.fromEntries(Object.entries(node.parameters).map(([k, v]) => [k, evaluate(v)])); + assert.equal(params.subscriptionKey, 'inbound-handoff-synthetic-workflow-17'); + const s = fake.server(); const staticData = {}; const methods = new PolymorfaTrigger().webhookMethods.default; + const context = { getCredentials: async () => fake.auth, getNodeWebhookUrl: () => 'https://n8n.example/webhook/inbound/webhook', getNodeParameter: k => params[k], getWorkflowStaticData: () => staticData, + helpers: { httpRequest: async r => { const res = await s.request({ method: r.method, url: r.url, headers: r.headers, query: r.qs, body: r.body }); return { statusCode: res.status, headers: res.headers, body: res.body }; } } }; + assert.equal(await methods.checkExists.call(context), false); + await methods.create.call(context); + assert.equal(s.rows.length, 1); assert.equal(staticData.subscription.subscriptionKey, params.subscriptionKey); +}); From bf30f04ccd3cfe0619a69bf4c16b8b46924046af Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Fri, 6 Nov 2026 05:41:06 +0200 Subject: [PATCH 14/14] Keep write recovery state in errors and n8n Continue On Fail items A 5xx on a write with a JSON error code, for example internal_error, lost the reconcile guidance, so a user might retry with a new key and send twice. Writes that end in a server error, and idempotency_in_progress, now always say to reconcile and keep the key and start time. Make modules pass the request method to their error handler for the same result. n8n Continue On Fail items now carry code, status, requestId and retryAfter from the sanitized error, so workflows can branch on recovery codes. --- CHANGELOG.md | 2 ++ make/functions/polymorfaAdvanced.js | 7 +++++-- make/functions/polymorfaBody.js | 7 +++++-- make/functions/polymorfaDetachHook.js | 2 +- make/functions/polymorfaEnvelope.js | 7 +++++-- make/functions/polymorfaError.js | 4 ++-- make/functions/polymorfaHook.js | 7 +++++-- make/functions/polymorfaRequest.js | 7 +++++-- make/functions/polymorfaResult.js | 13 ++++++++----- make/functions/polymorfaSegment.js | 7 +++++-- make/modules/apiRequest/communication.json | 12 +++++++++++- make/modules/listNumbers/communication.json | 12 +++++++++++- make/modules/listTemplates/communication.json | 12 +++++++++++- make/modules/react/communication.json | 12 +++++++++++- make/modules/sendMedia/communication.json | 12 +++++++++++- make/modules/sendTemplate/communication.json | 12 +++++++++++- make/modules/sendText/communication.json | 12 +++++++++++- make/webhooks/callEnded/attach.json | 3 +++ make/webhooks/messageReceived/attach.json | 3 +++ make/webhooks/messageState/attach.json | 3 +++ make/webhooks/numberState/attach.json | 3 +++ n8n/README.md | 6 ++++-- n8n/nodes/Polymorfa/Polymorfa.node.ts | 12 ++++++++++-- n8n/shared/core.js | 11 +++++++---- n8n/shared/hooks.js | 2 +- pipedream/common/core.cjs | 11 +++++++---- pipedream/common/hooks.cjs | 2 +- scripts/generate.mjs | 11 ++++++----- shared/core.cjs | 11 +++++++---- shared/hooks.cjs | 2 +- tests/core.test.cjs | 10 ++++++++++ tests/make.test.cjs | 8 ++++++++ tests/native.test.cjs | 16 +++++++++++++++- zapier/shared/core.cjs | 11 +++++++---- zapier/shared/hooks.cjs | 2 +- 35 files changed, 217 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63d4bd0..9a63920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Make: detach reads the recorded webhook by ID; unusual URLs on unrelated project webhooks no longer break attach. - Zapier: accept the documented `Http-` prefixed raw request header form. +- n8n Continue On Fail items include `code`, `status`, `requestId` and + `retryAfter`. Server errors on writes keep the reconcile guidance. - The n8n inbound handoff recipe derives its subscription key from the workflow ID. - Preserve definite subscription errors and non-JSON uncertain outcomes; normalize callback ownership and native Zapier response headers. diff --git a/make/functions/polymorfaAdvanced.js b/make/functions/polymorfaAdvanced.js index e9cb6d6..8f6b9fd 100644 --- a/make/functions/polymorfaAdvanced.js +++ b/make/functions/polymorfaAdvanced.js @@ -1,7 +1,10 @@ function polymorfaAdvanced(p, auth) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaBody.js b/make/functions/polymorfaBody.js index 7750229..93bd3ca 100644 --- a/make/functions/polymorfaBody.js +++ b/make/functions/polymorfaBody.js @@ -1,7 +1,10 @@ function polymorfaBody(action, p) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaDetachHook.js b/make/functions/polymorfaDetachHook.js index f82fdf1..e2ee6d8 100644 --- a/make/functions/polymorfaDetachHook.js +++ b/make/functions/polymorfaDetachHook.js @@ -1,7 +1,7 @@ function polymorfaDetachHook(status, body, id, projectId, ownedUrl, markerName, group) { if (status === 404) return false; -if (status !== 200) throw new Error(iml.polymorfaError(status, {}, body)); +if (status !== 200) throw new Error(iml.polymorfaError(status, {}, body, 'GET')); if (!body || !body.data || body.data.id !== id) throw new Error('Subscription ownership mismatch.'); return iml.polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group); } diff --git a/make/functions/polymorfaEnvelope.js b/make/functions/polymorfaEnvelope.js index 19385f7..ca1526e 100644 --- a/make/functions/polymorfaEnvelope.js +++ b/make/functions/polymorfaEnvelope.js @@ -1,7 +1,10 @@ function polymorfaEnvelope(body, group) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaError.js b/make/functions/polymorfaError.js index 7649952..ce41cd0 100644 --- a/make/functions/polymorfaError.js +++ b/make/functions/polymorfaError.js @@ -1,4 +1,4 @@ -function polymorfaError(status, headers, body) { +function polymorfaError(status, headers, body, method) { -try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; } +try { iml.polymorfaResult(status, headers, body, method); return 'Polymorfa invalid_response'; } catch (error) { return error.message; } } diff --git a/make/functions/polymorfaHook.js b/make/functions/polymorfaHook.js index 088c890..eb2c2bb 100644 --- a/make/functions/polymorfaHook.js +++ b/make/functions/polymorfaHook.js @@ -1,7 +1,10 @@ function polymorfaHook(auth, group, subscriptionKey) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaRequest.js b/make/functions/polymorfaRequest.js index 6104e45..e2db7b8 100644 --- a/make/functions/polymorfaRequest.js +++ b/make/functions/polymorfaRequest.js @@ -1,7 +1,10 @@ function polymorfaRequest(action, p, auth) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/functions/polymorfaResult.js b/make/functions/polymorfaResult.js index 7535b8b..884229b 100644 --- a/make/functions/polymorfaResult.js +++ b/make/functions/polymorfaResult.js @@ -1,7 +1,10 @@ -function polymorfaResult(status, headers, body) { +function polymorfaResult(status, headers, body, method) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; @@ -19,10 +22,10 @@ function parseResponse(response) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; - throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry, typeof response.method === 'string' && response.method.toUpperCase() !== 'GET'); } if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); return body; } -return parseResponse({ status, headers, body }); +return parseResponse({ status, headers, body, method }); } diff --git a/make/functions/polymorfaSegment.js b/make/functions/polymorfaSegment.js index 302c696..2e0e35c 100644 --- a/make/functions/polymorfaSegment.js +++ b/make/functions/polymorfaSegment.js @@ -1,7 +1,10 @@ function polymorfaSegment(value) { class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; diff --git a/make/modules/apiRequest/communication.json b/make/modules/apiRequest/communication.json index dcef270..79eaa59 100644 --- a/make/modules/apiRequest/communication.json +++ b/make/modules/apiRequest/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('apiRequest'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('apiRequest'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('apiRequest'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/listNumbers/communication.json b/make/modules/listNumbers/communication.json index d0a47a9..795a06a 100644 --- a/make/modules/listNumbers/communication.json +++ b/make/modules/listNumbers/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listNumbers'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listNumbers'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listNumbers'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/listTemplates/communication.json b/make/modules/listTemplates/communication.json index 5d071ab..5e7fcd5 100644 --- a/make/modules/listTemplates/communication.json +++ b/make/modules/listTemplates/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listTemplates'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listTemplates'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('listTemplates'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/react/communication.json b/make/modules/react/communication.json index 06db812..6af2e13 100644 --- a/make/modules/react/communication.json +++ b/make/modules/react/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('react'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('react'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('react'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/sendMedia/communication.json b/make/modules/sendMedia/communication.json index 2ad6416..82582d4 100644 --- a/make/modules/sendMedia/communication.json +++ b/make/modules/sendMedia/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendMedia'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendMedia'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendMedia'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/sendTemplate/communication.json b/make/modules/sendTemplate/communication.json index d24bc66..000eed2 100644 --- a/make/modules/sendTemplate/communication.json +++ b/make/modules/sendTemplate/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendTemplate'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendTemplate'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendTemplate'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/modules/sendText/communication.json b/make/modules/sendText/communication.json index 156174c..b581dc1 100644 --- a/make/modules/sendText/communication.json +++ b/make/modules/sendText/communication.json @@ -8,7 +8,17 @@ "followRedirect": false, "response": { "valid": "{{polymorfaResult(statusCode; headers; body) != null}}", - "output": "{{polymorfaResult(statusCode; headers; body)}}" + "output": "{{polymorfaResult(statusCode; headers; body)}}", + "error": { + "409": { + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendText'; parameters; connection); 'method'))}}" + }, + "429": { + "type": "RateLimitError", + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendText'; parameters; connection); 'method'))}}" + }, + "message": "{{polymorfaError(statusCode; headers; body; get(polymorfaRequest('sendText'; parameters; connection); 'method'))}}" + } }, "log": { "sanitize": [ diff --git a/make/webhooks/callEnded/attach.json b/make/webhooks/callEnded/attach.json index 28a21c9..3d8c4f8 100644 --- a/make/webhooks/callEnded/attach.json +++ b/make/webhooks/callEnded/attach.json @@ -36,6 +36,9 @@ "response": { "temp": { "createdId": "{{body.data.webhook.id}}" + }, + "error": { + "message": "{{polymorfaError(statusCode; headers; body; 'POST')}}" } } }, diff --git a/make/webhooks/messageReceived/attach.json b/make/webhooks/messageReceived/attach.json index 45d61c3..601fbf8 100644 --- a/make/webhooks/messageReceived/attach.json +++ b/make/webhooks/messageReceived/attach.json @@ -36,6 +36,9 @@ "response": { "temp": { "createdId": "{{body.data.webhook.id}}" + }, + "error": { + "message": "{{polymorfaError(statusCode; headers; body; 'POST')}}" } } }, diff --git a/make/webhooks/messageState/attach.json b/make/webhooks/messageState/attach.json index f8118b8..b4d9ef5 100644 --- a/make/webhooks/messageState/attach.json +++ b/make/webhooks/messageState/attach.json @@ -39,6 +39,9 @@ "response": { "temp": { "createdId": "{{body.data.webhook.id}}" + }, + "error": { + "message": "{{polymorfaError(statusCode; headers; body; 'POST')}}" } } }, diff --git a/make/webhooks/numberState/attach.json b/make/webhooks/numberState/attach.json index 3210f1b..35ff5d6 100644 --- a/make/webhooks/numberState/attach.json +++ b/make/webhooks/numberState/attach.json @@ -38,6 +38,9 @@ "response": { "temp": { "createdId": "{{body.data.webhook.id}}" + }, + "error": { + "message": "{{polymorfaError(statusCode; headers; body; 'POST')}}" } } }, diff --git a/n8n/README.md b/n8n/README.md index c013828..5386459 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -32,8 +32,10 @@ caps decoded bytes at 4 MiB. Provider/API bounds can be lower. IDs remain string With **Continue On Fail** enabled, successful batch items keep their results and failed items return an error paired to the original input. Later items continue. -Use the saved write identity when handling an error; do not replay successful -items with new keys. +Failed items carry `error` (a readable message), `code`, and when present +`status`, `requestId` and `retryAfter` (seconds). Branch on `code`, for example +`idempotency_completed` or `outcome_unknown`. Use the saved write identity when +handling an error; do not replay successful items with new keys. Give each trigger a unique, stable **Subscription Key**. The node registers a new webhook on each activation and keeps its creation state in workflow static diff --git a/n8n/nodes/Polymorfa/Polymorfa.node.ts b/n8n/nodes/Polymorfa/Polymorfa.node.ts index 0939889..8bb6f92 100644 --- a/n8n/nodes/Polymorfa/Polymorfa.node.ts +++ b/n8n/nodes/Polymorfa/Polymorfa.node.ts @@ -1,7 +1,7 @@ import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeProperties, INodeType, INodeTypeDescription } from 'n8n-workflow'; import { actions, fields } from '../../shared/catalog'; -import { execute, MAX_MEDIA_BYTES } from '../../shared/core'; +import { execute, MAX_MEDIA_BYTES, IntegrationError } from '../../shared/core'; import { transport } from './transport'; type Field = { label: string; required?: boolean; help?: string; options?: Record; default?: string; json?: boolean }; @@ -53,7 +53,15 @@ export class Polymorfa implements INodeType { result.push({ json: data, pairedItem: { item: i } }); } catch (error) { if (this.continueOnFail()) { - result.push({ json: { error: error instanceof Error ? error.message : 'Polymorfa request failed.' }, pairedItem: { item: i } }); + // Machine-readable state lets a workflow branch on recovery codes. IntegrationError fields hold only sanitized values. + const failure: Record = { error: error instanceof Error ? error.message : 'Polymorfa request failed.' }; + if (error instanceof IntegrationError) { + failure.code = error.code; + if (error.status) failure.status = error.status; + if (error.requestId) failure.requestId = error.requestId; + if (error.retryAfter !== undefined) failure.retryAfter = error.retryAfter; + } + result.push({ json: failure, pairedItem: { item: i } }); continue; } throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i }); diff --git a/n8n/shared/core.js b/n8n/shared/core.js index 59522cc..3d7596b 100644 --- a/n8n/shared/core.js +++ b/n8n/shared/core.js @@ -6,8 +6,11 @@ const VERSION = '2026-09-22'; const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; @@ -121,7 +124,7 @@ function parseResponse(response) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; - throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry, typeof response.method === 'string' && response.method.toUpperCase() !== 'GET'); } if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); return body; @@ -130,6 +133,6 @@ async function execute(action, p, auth, transport, provider, now) { const request = buildRequest(action, p, auth, provider, now); let response; try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method: request.method }); } module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/n8n/shared/hooks.js b/n8n/shared/hooks.js index 5ab9b17..9b68200 100644 --- a/n8n/shared/hooks.js +++ b/n8n/shared/hooks.js @@ -36,7 +36,7 @@ async function call(auth, transport, provider, method, path, body, key, query) { let response; try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method }); } async function find(auth, url, group, subscriptionKey, transport, provider) { let cursor; const seen = new Set(); const matches = []; diff --git a/pipedream/common/core.cjs b/pipedream/common/core.cjs index 59522cc..3d7596b 100644 --- a/pipedream/common/core.cjs +++ b/pipedream/common/core.cjs @@ -6,8 +6,11 @@ const VERSION = '2026-09-22'; const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; @@ -121,7 +124,7 @@ function parseResponse(response) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; - throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry, typeof response.method === 'string' && response.method.toUpperCase() !== 'GET'); } if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); return body; @@ -130,6 +133,6 @@ async function execute(action, p, auth, transport, provider, now) { const request = buildRequest(action, p, auth, provider, now); let response; try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method: request.method }); } module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/pipedream/common/hooks.cjs b/pipedream/common/hooks.cjs index f15231e..8b518a7 100644 --- a/pipedream/common/hooks.cjs +++ b/pipedream/common/hooks.cjs @@ -36,7 +36,7 @@ async function call(auth, transport, provider, method, path, body, key, query) { let response; try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method }); } async function find(auth, url, group, subscriptionKey, transport, provider) { let cursor; const seen = new Set(); const matches = []; diff --git a/scripts/generate.mjs b/scripts/generate.mjs index 93b36bf..08e70b6 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -61,7 +61,7 @@ function imlFunction(name, args, definitions, statement) { imlFunction('polymorfaBody', 'action, p', `${errorClass}\nconst MAX_MEDIA_BYTES = ${core.MAX_MEDIA_BYTES};\n${core.required}\n${core.conversation || ''}\n${core.messageBody}`, 'return messageBody(action, p);'); imlFunction('polymorfaAdvanced', 'p, auth', `${errorClass}\n${core.required}\n${core.object || ''}\nconst ORIGIN = '${core.ORIGIN}';\n${core.advanced || ''}`, 'return advanced(p, auth);'); imlFunction('polymorfaRequest', 'action, p, auth', `${errorClass}\nconst ORIGIN = '${core.ORIGIN}', VERSION = '${core.VERSION}', MAX_WRITE_AGE = ${23 * 60 * 60 * 1000};\n${core.required}\n${core.segment}\n${core.headers}\n${core.writeHeaders}\n${core.buildRequest.toString().replace('messageBody(action, p)', 'iml.polymorfaBody(action, p)').replace('advanced(p, auth)', 'iml.polymorfaAdvanced(p, auth)')}`, "return buildRequest(action, p, auth, 'make');"); -imlFunction('polymorfaResult', 'status, headers, body', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body });'); +imlFunction('polymorfaResult', 'status, headers, body, method', `${errorClass}\n${safeCode}\n${core.parseResponse}`, 'return parseResponse({ status, headers, body, method });'); imlFunction('polymorfaSegment', 'value', `${errorClass}\n${core.required}\n${core.segment}`, "return segment(value, 'resource_id');"); imlFunction('polymorfaHook', 'auth, group, subscriptionKey', `${errorClass}\n${core.required}\n${core.segment}\nconst events = ${JSON.stringify(events)};`, `if (!events[group]) throw new IntegrationError('invalid_event_group');\nrequired(subscriptionKey, 'subscription_key');\nif (subscriptionKey.length > 200) throw new IntegrationError('invalid_subscription_key');\nconst scope = JSON.stringify(['polymorfa-webhook-v1', segment(auth.projectId, 'project_id'), group, subscriptionKey]);\nconst derive = label => iml.sha256(label + ':' + scope, 'hex', auth.apiKey);\nreturn { name: 'X-Polymorfa-Integration-Token-' + derive('marker').slice(0, 24), value: derive('receiver'), key: derive('create') };`); imlFunction('polymorfaCreateKey', 'key, nonce', '', `if (!/^[a-f0-9]{64}$/.test(key) || typeof nonce !== 'string' || !/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid webhook creation nonce.');\nreturn 'integration-create-' + iml.sha256(nonce, 'hex', key);`); @@ -70,12 +70,12 @@ imlFunction('polymorfaCreationNonce', 'url', '', `if (typeof url !== 'string' || imlFunction('polymorfaEnvelope', 'body, group', `${errorClass}\nconst events = ${JSON.stringify(events)};\n${hooks.envelope}`, 'return envelope(body, group);'); imlFunction('polymorfaLiveHook', 'body, auth, url, group, key, expectedId', '', `const id = iml.polymorfaFindHook(body, auth, url, group, key);\nif (!id || id !== expectedId) throw new Error('Subscription receipt needs reconciliation.');\nreturn id;`); imlFunction('polymorfaCallback', 'value', '', `if (typeof value !== 'string' || /[\\s\\\\\\x00-\\x1f\\x7f]/.test(value)) throw new Error('Invalid callback.');\nconst parts = value.match(/^https:\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?$/i);\nif (!parts || parts[1].includes('@')) throw new Error('Invalid callback.');\nreturn 'https://' + parts[1].toLowerCase().replace(/:443$/, '') + (parts[2] || '/') + (parts[3] || '');`); -imlFunction('polymorfaError', 'status, headers, body', '', `try { iml.polymorfaResult(status, headers, body); return 'Polymorfa invalid_response'; } catch (error) { return error.message; }`); +imlFunction('polymorfaError', 'status, headers, body, method', '', `try { iml.polymorfaResult(status, headers, body, method); return 'Polymorfa invalid_response'; } catch (error) { return error.message; }`); imlFunction('polymorfaReceive', 'expectedName, expectedValue, group, headers, body', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};`, `const entries = Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === String(expectedName).toLowerCase());\nif (!expectedValue || entries.length !== 1 || entries[0][1] !== expectedValue) return false;\nreturn !!(body && typeof body.id === 'string' && body.id && typeof body.session === 'string' && body.session && types[group] && types[group].includes(body.event) && typeof body.timestamp === 'string' && Number.isFinite(Date.parse(body.timestamp)) && body.payload && typeof body.payload === 'object' && !Array.isArray(body.payload));`); imlFunction('polymorfaFindHook', 'body, auth, url, group, key', `const expected = iml.polymorfaHook(auth, group, key);\nconst types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\n// Match shared sameCallback: an invalid URL on any hook is simply not a match.\nconst same = h => { try { return iml.polymorfaCallback(h.url) === iml.polymorfaCallback(url); } catch (error) { return false; } };`, `if (!Array.isArray(body.data) || body.page?.hasMore) throw new Error('Subscription inventory needs reconciliation.');\nif (body.data.some(h => !same(h) && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()))) throw new Error('Subscription key is already in use.');\nconst owns = h => h.projectId === auth.projectId && h.owner === 'project' && same(h) && h.format === 'native' && h.enabled && h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase()) && h.eventTypes?.length === types[group].length && types[group].every(t => h.eventTypes.includes(t));\nif (body.data.some(h => (same(h) || h.headers?.some(x => x.name.toLowerCase() === expected.name.toLowerCase())) && !owns(h))) throw new Error('Changed subscription needs reconciliation.');\nconst matches = body.data.filter(owns);\nif (matches.length > 1) throw new Error('Duplicate owned subscriptions need reconciliation.');\nreturn matches[0]?.id || null;`); imlFunction('polymorfaOwnedHook', 'body, projectId, ownedUrl, markerName, group', `const types = ${JSON.stringify(Object.fromEntries(Object.entries(events).map(([k, e]) => [k, e.types])))};\nconst same = (a, b) => { try { return iml.polymorfaCallback(a) === iml.polymorfaCallback(b); } catch (error) { return false; } };`, `const h = body.data;\nif (!h || h.owner !== 'project' || h.projectId !== projectId || !same(h.url, ownedUrl) || h.format !== 'native' || !h.headers?.some(x => x.name.toLowerCase() === markerName.toLowerCase()) || h.eventTypes?.length !== types[group]?.length || !types[group].every(t => h.eventTypes.includes(t))) throw new Error('Subscription ownership mismatch.');\nreturn true;`); // Detach reads the one recorded webhook by ID. 404 means it is already gone. -imlFunction('polymorfaDetachHook', 'status, body, id, projectId, ownedUrl, markerName, group', '', `if (status === 404) return false;\nif (status !== 200) throw new Error(iml.polymorfaError(status, {}, body));\nif (!body || !body.data || body.data.id !== id) throw new Error('Subscription ownership mismatch.');\nreturn iml.polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group);`); +imlFunction('polymorfaDetachHook', 'status, body, id, projectId, ownedUrl, markerName, group', '', `if (status === 404) return false;\nif (status !== 200) throw new Error(iml.polymorfaError(status, {}, body, 'GET'));\nif (!body || !body.data || body.data.id !== id) throw new Error('Subscription ownership mismatch.');\nreturn iml.polymorfaOwnedHook(body, projectId, ownedUrl, markerName, group);`); emit('make/app.json', { name: 'polymorfa', label: 'Polymorfa', version: 1, theme: '#7657ff', language: 'en', description: 'Send messages and receive project events with Polymorfa.' }); const makeError = '{{polymorfaError(statusCode; headers; body)}}'; @@ -89,7 +89,8 @@ for (const [key, action] of Object.entries(actions)) { return { name, type: f.options ? 'select' : 'text', label: f.label, required: !!f.required || (key !== 'apiRequest' && ['idempotencyKey', 'writeCreatedAt'].includes(name)), ...(f.default ? { default: f.default } : {}), ...(f.help ? { help: f.help } : {}), ...(f.options ? { options: Object.entries(f.options).map(([value, label]) => ({ value, label })) } : {}) }; })); const r = `polymorfaRequest('${key}'; parameters; connection)`; - emit(`make/modules/${key}/communication.json`, { url: `{{get(${r}; 'url')}}`, method: `{{get(${r}; 'method')}}`, headers: `{{get(${r}; 'headers')}}`, qs: `{{get(${r}; 'query')}}`, body: `{{get(${r}; 'body')}}`, type: 'json', followRedirect: false, response: { valid: '{{polymorfaResult(statusCode; headers; body) != null}}', output: '{{polymorfaResult(statusCode; headers; body)}}' }, log: { sanitize: ['request.headers.authorization', 'request.headers.idempotency-key'] } }); + const moduleError = `{{polymorfaError(statusCode; headers; body; get(${r}; 'method'))}}`; + emit(`make/modules/${key}/communication.json`, { url: `{{get(${r}; 'url')}}`, method: `{{get(${r}; 'method')}}`, headers: `{{get(${r}; 'headers')}}`, qs: `{{get(${r}; 'query')}}`, body: `{{get(${r}; 'body')}}`, type: 'json', followRedirect: false, response: { valid: '{{polymorfaResult(statusCode; headers; body) != null}}', output: '{{polymorfaResult(statusCode; headers; body)}}', error: { message: moduleError, '409': { message: moduleError }, '429': { type: 'RateLimitError', message: moduleError } } }, log: { sanitize: ['request.headers.authorization', 'request.headers.idempotency-key'] } }); const spec = key === 'listNumbers' ? specs.platform : specs.messaging; const response = key === 'listNumbers' ? { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/PlatformSession' } } } } : spec.components.schemas[key === 'listTemplates' ? 'ListProjectTemplatesResponse' : key === 'react' ? 'SendReactionResponse' : 'SendMessageResponse']; emit(`make/modules/${key}/interface.json`, key === 'apiRequest' ? [{ name: 'data', label: 'Result', type: 'collection' }] : interfaceFields(response, spec)); @@ -115,7 +116,7 @@ for (const [key, e] of Object.entries(events)) { const markerField = field => `{{get(${marker}; '${field}')}}`; emit(`make/webhooks/${key}/attach.json`, [ { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { temp: { ownedId: `{{polymorfaFindHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey)}}`, nonce: '{{polymorfaCreationNonce(webhook.url)}}' } } }, - { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); temp.nonce)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { temp: { createdId: '{{body.data.webhook.id}}' } } }, + { condition: '{{!temp.ownedId}}', url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'POST', headers: { 'Idempotency-Key': `{{polymorfaCreateKey(get(${marker}; 'key'); temp.nonce)}}` }, body: { url: '{{webhook.url}}', eventTypes: e.types, format: 'native', enabled: true, headers: [{ name: markerField('name'), value: markerField('value') }] }, response: { temp: { createdId: '{{body.data.webhook.id}}' }, error: { message: "{{polymorfaError(statusCode; headers; body; 'POST')}}" } } }, { url: '/platform/projects/{{polymorfaSegment(connection.projectId)}}/webhooks', method: 'GET', qs: { limit: 100 }, response: { data: { externalHookId: `{{polymorfaLiveHook(body; connection; webhook.url; '${key}'; parameters.subscriptionKey; ifempty(temp.ownedId; temp.createdId))}}`, ownedUrl: '{{webhook.url}}', receiverName: markerField('name'), receiverValue: markerField('value') } } }, ]); emit(`make/webhooks/${key}/detach.json`, [ diff --git a/shared/core.cjs b/shared/core.cjs index 72ec64e..5288dff 100644 --- a/shared/core.cjs +++ b/shared/core.cjs @@ -5,8 +5,11 @@ const VERSION = '2026-09-22'; const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; @@ -120,7 +123,7 @@ function parseResponse(response) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; - throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry, typeof response.method === 'string' && response.method.toUpperCase() !== 'GET'); } if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); return body; @@ -129,6 +132,6 @@ async function execute(action, p, auth, transport, provider, now) { const request = buildRequest(action, p, auth, provider, now); let response; try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method: request.method }); } module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/shared/hooks.cjs b/shared/hooks.cjs index c28a92b..68538a6 100644 --- a/shared/hooks.cjs +++ b/shared/hooks.cjs @@ -35,7 +35,7 @@ async function call(auth, transport, provider, method, path, body, key, query) { let response; try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method }); } async function find(auth, url, group, subscriptionKey, transport, provider) { let cursor; const seen = new Set(); const matches = []; diff --git a/tests/core.test.cjs b/tests/core.test.cjs index b84fbd5..ca50d23 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -97,3 +97,13 @@ test('public surface excludes unlaunched Functions and message-operation lookups assert.ok(!('runFunction' in actions)); assert.ok(!('getMessageOperation' in actions)); }); module.exports = { auth, input, now }; + +test('a server error on a write keeps reconcile guidance even when the body carries a JSON error code', async () => { + for (const status of [500, 503]) { + const transport = async () => ({ status, headers: { 'x-request-id': 'safe-request' }, body: { error: { code: 'internal_error', message: auth.apiKey } } }); + await assert.rejects(core.execute('sendText', input, auth, transport, 'test', now), e => e.code === 'internal_error' && e.status === status && /Reconcile the original write before retrying\. Keep its key and start time\./.test(e.message) && !e.message.includes(auth.apiKey)); + await assert.rejects(core.execute('listNumbers', {}, auth, transport, 'test', now), e => e.code === 'internal_error' && !/Reconcile/.test(e.message)); + } + const inProgress = async () => ({ status: 409, headers: { 'retry-after': '5' }, body: { error: { code: 'idempotency_in_progress' } } }); + await assert.rejects(core.execute('sendText', input, auth, inProgress, 'test', now), e => e.retryAfter === 5 && /Reconcile/.test(e.message) && /Wait at least 5 seconds/.test(e.message)); +}); diff --git a/tests/make.test.cjs b/tests/make.test.cjs index 10e1d28..9d2824c 100644 --- a/tests/make.test.cjs +++ b/tests/make.test.cjs @@ -135,6 +135,14 @@ test('Make non-2xx error handlers retain recovery and rate-limit guidance withou assert.match(iml.polymorfaError(409, {}, { error: { code: 'idempotency_completed', message: auth.apiKey } }), /Recover its original result/); assert.match(iml.polymorfaError(429, { 'retry-after': '19' }, { error: { code: 'rate_limited' } }), /19 seconds/); assert.match(iml.polymorfaError(502, {}, 'gateway error'), /Reconcile/); + assert.match(iml.polymorfaError(503, {}, { error: { code: 'internal_error' } }, 'POST'), /Reconcile the original write/); + assert.doesNotMatch(iml.polymorfaError(503, {}, { error: { code: 'internal_error' } }, 'GET'), /Reconcile/); + for (const name of readdirSync('make/modules')) { + if (JSON.parse(readFileSync(`make/modules/${name}/metadata.json`)).type !== 'action') continue; + const communication = JSON.parse(readFileSync(`make/modules/${name}/communication.json`)); + assert.ok(communication.response.error.message.includes("polymorfaError(statusCode; headers; body; get(polymorfaRequest(")); + assert.equal(communication.response.error['429'].type, 'RateLimitError'); + } const error = JSON.parse(readFileSync('make/base.json')).response.error; assert.ok(error['409'].message.includes('polymorfaError')); assert.equal(error['429'].type, 'RateLimitError'); assert.equal(error['5xx'], undefined); }); diff --git a/tests/native.test.cjs b/tests/native.test.cjs index f96e4a7..2585b03 100644 --- a/tests/native.test.cjs +++ b/tests/native.test.cjs @@ -60,7 +60,7 @@ test('n8n continue-on-fail retains successful write results and continues later const [result] = await new Polymorfa().execute.call(context); assert.equal(requests, 2); assert.equal(result.length, 3); assert.equal(result[0].json.data.id, '1'); assert.equal(result[2].json.data.id, '2'); - assert.match(result[1].json.error, /invalid_conversation_id/); assert.deepEqual(result[1].pairedItem, { item: 1 }); + assert.match(result[1].json.error, /invalid_conversation_id/); assert.equal(result[1].json.code, 'invalid_conversation_id'); assert.deepEqual(result[1].pairedItem, { item: 1 }); context.continueOnFail = () => false; await assert.rejects(new Polymorfa().execute.call(context)); }); @@ -93,6 +93,20 @@ test('Zapier editor permits removal and sends an explicit empty reaction when th for (const trigger of Object.values(zapier.triggers)) assert.deepEqual(trigger.operation.inputFields.map(f => f.key), ['subscriptionKey']); }); +test('n8n continue-on-fail items carry code, status, request ID and retry delay without secrets', async () => { + const context = { getCredentials: async () => auth, getInputData: () => [{ json: {} }], continueOnFail: () => true, + getNodeParameter: (key, i, fallback) => input[key] ?? fallback, getNode: () => ({ name: 'test', typeVersion: 1 }), + helpers: { httpRequest: async () => ({ statusCode: 429, headers: { 'retry-after': '9', 'x-request-id': 'safe-request' }, body: { error: { code: 'rate_limited', message: auth.apiKey } } }) } }; + const [[item]] = await new Polymorfa().execute.call(context); + assert.equal(item.json.code, 'rate_limited'); assert.equal(item.json.status, 429); + assert.equal(item.json.requestId, 'safe-request'); assert.equal(item.json.retryAfter, 9); + assert.ok(!JSON.stringify(item.json).includes(auth.apiKey)); + assert.deepEqual(Object.keys(item.json).sort(), ['code', 'error', 'requestId', 'retryAfter', 'status']); + context.helpers.httpRequest = async () => ({ statusCode: 400, headers: {}, body: { error: { code: 'invalid_request' } } }); + const [[plain]] = await new Polymorfa().execute.call(context); + assert.deepEqual(Object.keys(plain.json).sort(), ['code', 'error', 'status']); +}); + function n8nHookContext(s, staticData, params) { return { getCredentials: async () => fake.auth, getNodeWebhookUrl: () => params.url, getNodeParameter: k => params[k], getWorkflowStaticData: () => staticData, helpers: { httpRequest: async r => { const res = await s.request({ method: r.method, url: r.url, headers: r.headers, query: r.qs, body: r.body }); return { statusCode: res.status, headers: res.headers, body: res.body }; } } }; diff --git a/zapier/shared/core.cjs b/zapier/shared/core.cjs index 59522cc..3d7596b 100644 --- a/zapier/shared/core.cjs +++ b/zapier/shared/core.cjs @@ -6,8 +6,11 @@ const VERSION = '2026-09-22'; const MAX_MEDIA_BYTES = 4 * 1024 * 1024; const MAX_WRITE_AGE = 23 * 60 * 60 * 1000; class IntegrationError extends Error { - constructor(code, status = 0, requestId, retryAfter) { - const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : /unknown|reconciliation/.test(code) ? ' Reconcile the original write before retrying. Keep its key and start time.' : retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + constructor(code, status = 0, requestId, retryAfter, write = false) { + // A write that ended in a server error or is still running has an uncertain outcome, whatever its code. + const uncertain = /unknown|reconciliation/.test(code) || code === 'idempotency_in_progress' || (write && status >= 500); + const wait = retryAfter !== undefined ? ` Wait at least ${retryAfter} seconds; preserve the same key and start time.` : ''; + const recovery = code === 'idempotency_completed' ? ' The original write completed. Recover its original result; do not send again with a new key.' : code === 'subscription_receipt_deleted' ? ' The webhook created by this activation was deleted. Activate the trigger again to register a new one.' : uncertain ? ' Reconcile the original write before retrying. Keep its key and start time.' + wait : wait; super(`Polymorfa ${code}${status ? ` (HTTP ${status})` : ''}${requestId ? `. Request: ${requestId}` : ''}.${recovery}`); this.name = 'IntegrationError'; this.code = code; this.status = status; this.requestId = requestId; this.retryAfter = retryAfter; @@ -121,7 +124,7 @@ function parseResponse(response) { const code = safeCode(body?.error?.code, status >= 500 ? 'outcome_unknown' : status >= 300 && status < 400 ? 'redirect_refused' : 'request_failed'); const h = Object.fromEntries(Object.entries(response.headers || {}).map(([k, v]) => [k.toLowerCase(), String(v)])); const retry = /^\d+$/.test(h['retry-after'] || '') ? Number(h['retry-after']) : undefined; - throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry); + throw new IntegrationError(code, status, safeCode(body?.error?.request_id || h['x-request-id'], undefined), retry, typeof response.method === 'string' && response.method.toUpperCase() !== 'GET'); } if (!body || typeof body !== 'object') throw new IntegrationError('invalid_response', status); return body; @@ -130,6 +133,6 @@ async function execute(action, p, auth, transport, provider, now) { const request = buildRequest(action, p, auth, provider, now); let response; try { response = await transport(request); } catch { throw new IntegrationError(request.method === 'GET' ? 'network_error' : 'outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method: request.method }); } module.exports = { ORIGIN, VERSION, MAX_MEDIA_BYTES, IntegrationError, required, segment, object, conversation, advanced, headers, writeHeaders, messageBody, buildRequest, parseResponse, execute }; diff --git a/zapier/shared/hooks.cjs b/zapier/shared/hooks.cjs index f15231e..8b518a7 100644 --- a/zapier/shared/hooks.cjs +++ b/zapier/shared/hooks.cjs @@ -36,7 +36,7 @@ async function call(auth, transport, provider, method, path, body, key, query) { let response; try { response = await transport({ method, url: ORIGIN + path, headers: { ...headers(auth, provider), ...(key ? { 'Idempotency-Key': key } : {}) }, ...(body ? { body } : {}), ...(query ? { query } : {}) }); } catch { throw new IntegrationError(method === 'GET' ? 'network_error' : 'subscription_outcome_unknown'); } - return parseResponse(response); + return parseResponse({ ...response, method }); } async function find(auth, url, group, subscriptionKey, transport, provider) { let cursor; const seen = new Set(); const matches = [];