diff --git a/.eslintrc.js b/.eslintrc.js index 8b5c634..20c84e7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,7 +18,7 @@ module.exports = { extraFileExtensions: ['.json'], }, - ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'], + ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**', '**/*.test.ts', '**/testHelpers.ts'], overrides: [ { diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 581da8b..f8dcd48 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -13,6 +13,11 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + # backward-compatibility-check builds a worktree at the deployed + # baseline commit, so the full history (not a shallow clone) must + # be available. + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -29,3 +34,9 @@ jobs: - name: Run build run: npm run build + - name: Run tests + run: npm test + + - name: Backward-compatibility contract check + run: npm run backward-compatibility-check + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a6066d8..b5579e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,9 +17,10 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '22' + node-version-file: '.nvmrc' + cache: 'npm' - run: npm install -g npm@latest - run: npm ci - run: npm run build --if-present - run: npm run lint - - run: npm publish + - run: npm publish --provenance diff --git a/.gitignore b/.gitignore index a0b6585..2dbd67d 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,10 @@ vite.config.ts.timestamp-* # IDE .idea + +# Codex +.codex + +# Local scratch / temp files (not for commit) +.local/ +examples/ \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 5f53e87..eb6ead3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.19.0 +v24.16.0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2cbb008 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,39 @@ +# AGENTS.md + +Guidance for coding agents working in this repository. This is an n8n **community node** package — a single node (`n8n-nodes-cloudinary.cloudinary`) and a single credential type (`cloudinaryApi`). No service layer, no SDK; zero runtime deps beyond the `n8n-workflow` peer dep. + +Deep references live in [docs/](docs/) and are linked inline below — read them when a task touches that area, so this file stays small. + +## Commands + +- `npm run build` — compile TypeScript and copy SVG/PNG icons into `dist/` via gulp. +- `npm run dev` — `tsc --watch`. Does not re-run the icon copy; if icons change, re-run `build`. +- `npm run lint` / `npm run lintfix` — ESLint (`eslint-plugin-n8n-nodes-base`) over `nodes`, `credentials`, `package.json`. +- `npm run format` — Prettier over `nodes` and `credentials`. +- `npm run n8n-validate` — `@n8n/scan-community-package`; required to pass before publishing. +- `npm run prepublishOnly` — build + stricter lint (`.eslintrc.prepublish.js`); runs automatically on `npm publish`. +- `npm test` — run the Vitest suite once. `npm run test:watch` for watch mode. + +## Core rules + +These bite often; the linked docs carry the full reasoning. + +- **No runtime dependencies — ever.** n8n forbids verified community nodes from declaring any runtime `dependencies`; `package.json` must carry only `devDependencies` and the `n8n-workflow` peer dep. This is a hard publishing gate, not a style preference — don't reach for an SDK or helper library; hand-roll it (see the pure-JS SHA-256 and multipart builders) or it won't pass verification. → [n8n verification rules](https://docs.n8n.io/integrations/community-nodes/build-community-nodes/#submit-your-node-for-verification-by-n8n) +- **Tests use Vitest, not Jest.** `*.test.ts` is excluded from `tsconfig.json`, so any shared test util importing `vitest` must also be excluded or it leaks into `dist/`. → [docs/architecture.md#testing](docs/architecture.md#testing) +- **Three interaction flows** (signed Upload API / HTTP Basic auth / delivery-URL construction with no API call) — pick the right one when adding an op. → [docs/architecture.md#three-flows](docs/architecture.md#three-flows) +- **Transformation logic lives in the component builders**, not the handlers — Multi-Step is a second consumer and must not drift. Change the builder, not a handler. → [docs/transforms.md](docs/transforms.md) +- **Mirror the Cloudinary API for field/option/output names** (`type`, `public_id`, `resource_type`, …) — don't invent or prefix when an API name exists. → [docs/conventions.md#naming-mirror-the-cloudinary-api](docs/conventions.md#naming-mirror-the-cloudinary-api) +- **Saved-workflow JSON is a public contract** — evolve additively or bump `typeVersion`; never rename a param `name` or option `value`. → [docs/backwards-compat.md](docs/backwards-compat.md) +- **Structured metadata is a pipe-separated string, not JSON** — always go through `metadataToPipeString`. → [docs/conventions.md#structured-metadata-format](docs/conventions.md#structured-metadata-format) + +## Architecture at a glance + +A declarative node class + a per-operation handler map + small util files: + +- [Cloudinary.node.ts](nodes/Cloudinary/Cloudinary.node.ts) — `INodeTypeDescription`; fields from [descriptions/](nodes/Cloudinary/descriptions/) drive the UI via `displayOptions.show` on `resource` + `operation`. `execute()` is a thin loop: resolve creds → look up `operationHandlers[`${resource}:${operation}`]` → wrap the returned JSON into `INodeExecutionData`. +- [operations/](nodes/Cloudinary/operations/) — one file per operation, grouped by resource; each exports an `OperationHandler`. [operations/index.ts](nodes/Cloudinary/operations/index.ts) maps `${resource}:${operation}` → handler. +- [cloudinary.utils.ts](nodes/Cloudinary/cloudinary.utils.ts) — signing, multipart, URL/delivery builders, metadata serialization, error extraction. + +**Adding an operation:** (1) add it to the matching `operation` options block + any fields it needs (with the right `displayOptions.show`) under [descriptions/](nodes/Cloudinary/descriptions/); (2) drop a handler file in `operations//`; (3) register it in [operations/index.ts](nodes/Cloudinary/operations/index.ts). No change to `execute()` needed. + +The full file map, the three interaction flows, the testing setup, n8n integration points, and the build-output contract are in **[docs/architecture.md](docs/architecture.md)**. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..87d42a1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +# CLAUDE.md + +Project guidance for coding agents lives in [AGENTS.md](AGENTS.md) (the cross-agent standard). The line below imports it so Claude Code loads it as memory; keep all content in `AGENTS.md` and the `docs/` it links. + +@AGENTS.md diff --git a/README.md b/README.md index bcb1fe8..68e4252 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ On this page, you'll find a list of operations the Cloudinary node supports. * Update asset metadata fields * Get tags * Get structured metadata definitions + * Search assets + +### Search assets + +Uses the [Cloudinary Search API](https://cloudinary.com/documentation/search_api) to find assets by tag, folder, metadata, or any supported expression (e.g. `tags=cat AND uploaded_at>1d`, `folder:products/*`, `tags="back to school"`). + +The node emits **one n8n item per matching asset**, so downstream nodes can map over results directly without a Split Out step. + +- **Resource Types** — Cloudinary's Search API defaults to image-only when no `resource_type:` clause is in the expression. This node injects the clause automatically based on the Resource Types you select (defaults to `image`). To search across all types, select all three. If your expression already contains a `resource_type:` clause, the selection is ignored. +- **Return All** — when enabled, the node automatically paginates through Cloudinary's `next_cursor` until all matching assets have been returned. When disabled, only the first page (up to *Max Results*, capped at 500) is returned. +- **Rate limits** — the node surfaces `429`/`420` responses with a clear "rate limit exceeded" error including the server's `Retry-After`. +- **Eventual consistency** — newly uploaded assets may take a few seconds to appear in search results. Avoid searching for something you just uploaded in the same workflow without a delay. ## Supported authentication methods @@ -34,6 +46,10 @@ If you're a user with a Master admin, Admin, or Technical admin role, you can fi 4. From the top of the page copy the **Cloud name**. 5. Enter the cloud name, api key and api secret to your n8n credential. +#### Private CDN / custom delivery hostname (advanced) + +Most users can skip this. If your organization is on a **Advanced plan** that uses a [private CDN distribution or a custom delivery hostname (CNAME)](https://cloudinary.com/documentation/advanced_url_delivery_options#private_cdns_and_custom_delivery_hostnames_cnames), enable **Private CDN** in the credential and enter your **Custom Delivery Hostname** so the node builds delivery URLs against your private distribution instead of the default `res.cloudinary.com`. Leave these off if you're unsure — they don't apply to standard accounts. + ## Related resources diff --git a/credentials/CloudinaryApi.credentials.ts b/credentials/CloudinaryApi.credentials.ts index 06488fc..0436235 100644 --- a/credentials/CloudinaryApi.credentials.ts +++ b/credentials/CloudinaryApi.credentials.ts @@ -30,6 +30,24 @@ export class CloudinaryApi implements ICredentialType { default: '', typeOptions: { password: true }, }, + { + displayName: 'Private CDN', + name: 'privateCdn', + type: 'boolean', + default: false, + description: + 'Whether your account delivers from a private CDN distribution (-res.cloudinary.com). Only affects the delivery URLs built by Transform operations.', + }, + { + displayName: 'Custom Delivery Hostname', + name: 'secureDistribution', + type: 'string', + default: '', + placeholder: 'assets.example.com', + description: + 'Custom delivery hostname (CNAME) for your private CDN account. Leave empty to use the default -res.cloudinary.com subdomain.', + displayOptions: { show: { privateCdn: [true] } }, + }, ]; // This tells how this credential is authenticated diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..bcccdf2 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,40 @@ +# Architecture + +This is an n8n **community node** package with a single node and a single credential type. There's no service layer and no SDK dependency. The runtime is a declarative node class, a per-operation handler map, and small util files. + +## File map + +- [nodes/Cloudinary/Cloudinary.node.ts](../nodes/Cloudinary/Cloudinary.node.ts) — declares the node via `INodeTypeDescription` (properties come from [descriptions/](../nodes/Cloudinary/descriptions/) and drive the entire n8n UI through `displayOptions.show` conditionals on `resource` + `operation`). `execute()` is a thin loop over input items: it resolves credentials, looks up `operationHandlers[`${resource}:${operation}`]`, calls the handler, and wraps each returned JSON object into an `INodeExecutionData` with the right `pairedItem`. +- [nodes/Cloudinary/operations/](../nodes/Cloudinary/operations/) — one file per operation, grouped by resource (`upload/`, `updateAsset/`, `asset/`, `admin/`, `transform/`, `widget/`), each exporting an `OperationHandler` (`(ctx, i, creds) => Promise` — see [operations/types.ts](../nodes/Cloudinary/operations/types.ts)). [operations/index.ts](../nodes/Cloudinary/operations/index.ts) maps `${resource}:${operation}` to its handler. **Adding an operation:** (1) add it to the matching `operation` options block and any fields it needs (with the right `displayOptions.show`) under [descriptions/](../nodes/Cloudinary/descriptions/), (2) drop a handler file in `operations//`, (3) register it in [operations/index.ts](../nodes/Cloudinary/operations/index.ts). No change to `execute()` is needed. +- [nodes/Cloudinary/cloudinary.utils.ts](../nodes/Cloudinary/cloudinary.utils.ts) — shared helpers used by the handlers: `generateCloudinarySignature`, `createMultipartBody` (hand-rolled multipart so the package has no `form-data` dependency), the URL builders (`buildUploadUrl`, `buildResourceUpdateUrl`), the delivery-URL builders (`buildDeliveryUrl`, `joinTransformation`), `metadataToPipeString`, `buildSearchExpression`, and `extractCloudinaryError`. Signature scheme and delivery-URL details are in **Three flows** below. +- [nodes/Cloudinary/sha256.utils.ts](../nodes/Cloudinary/sha256.utils.ts) — pure-JS SHA-256 so the package has zero runtime deps beyond the `n8n-workflow` peer dep. +- [credentials/CloudinaryApi.credentials.ts](../credentials/CloudinaryApi.credentials.ts) — `cloudName` / `apiKey` / `apiSecret`, plus optional `privateCdn` / `secureDistribution` (custom delivery hostname/CNAME) consumed only by `buildDeliveryUrl`. Referenced by name `cloudinaryApi` from `Cloudinary.CREDENTIAL_TYPE`. + +## Three flows + +The node mixes three Cloudinary interaction styles depending on endpoint — keep this distinction when adding operations: + +1. **Signed Upload API** (`/v1_1/{cloud}/{resource_type}/upload`) — used by `upload.uploadUrl` and `upload.uploadFile`. Build a params object, call `generateCloudinarySignature`, append `signature`, then POST as `application/x-www-form-urlencoded` (URL upload) or `multipart/form-data` via `createMultipartBody` (file upload). The `file` field and `api_key` must be excluded from the signature payload. +2. **HTTP Basic auth** (`api_key:api_secret`) — used by everything under Admin API and Update Asset (`/tags/...`, `/metadata_fields`, `/resources/{type}/{storage_type}/{public_id}`). Passed via the `auth` option on `IHttpRequestOptions`; no signature. +3. **Delivery-URL construction (no API call)** — used by every `transform.*` operation. The handler reads fields, composes a transformation string (`joinTransformation`), and returns a delivery URL built by `buildDeliveryUrl` — it makes **no** `httpRequestWithAuthentication` call. The host is not always `res.cloudinary.com`: `buildDeliveryUrl` resolves shared (cloud in path) / private-CDN (`-res.cloudinary.com`) / custom-hostname-CNAME (cloud absent) from the optional `privateCdn` / `secureDistribution` credentials, mirroring the SDKs' explicit-config approach. Signed delivery (`s--SIG--`, for private/authenticated/strict assets) is intentionally **not** implemented yet — the Delivery Type field carries a non-prominent disclaimer. Because these handlers make no HTTP call, their tests assert the **returned JSON** (`secure_url` + `transformation`), not a request spy — `makeCtx` already supplies the only surface they touch (`getNodeParameter` + `getNode`). Cloudinary builds delivery paths as `.` with public_id opaque, so a public_id that carries its own extension (e.g. `my_image1234.png`) needs the format re-appended (`…png.png`) or Cloudinary reads the id's extension as the format and 404s; named ops carry no format, so `trailingMediaFormat` (in `operations/transform/shared.ts`) recovers it from the public_id's trailing media extension. Signed/authenticated source assets still can't be transformed (signing not implemented). + +## Testing + +Tests use **Vitest** (not Jest, despite the n8n ecosystem leaning Jest). Two layers: + +- **Pure-function unit tests** over the helpers in [cloudinary.utils.ts](../nodes/Cloudinary/cloudinary.utils.ts) — signing, URL builders, error extraction, metadata serialization. +- **Operation-handler tests** that mock `IExecuteFunctions` and assert the *request contract* (URL, method, `auth` vs `signature`, body shape) handed to `httpRequestWithAuthentication` — no real network calls. Shared mock builder and request-extraction helpers live in [nodes/Cloudinary/operations/testHelpers.ts](../nodes/Cloudinary/operations/testHelpers.ts). Note: because handlers call the HTTP helper via `.call(ctx, TYPE, options)`, the spy records args as `[TYPE, options]` — `this` is not in the args array. + +[vitest.config.ts](../vitest.config.ts) aliases `n8n-workflow` to its built CJS entry (`n8n-workflow/dist/index.js`). This is required, not a hack: the package's `import` condition points at raw `src/index.ts`, which Vitest can't load. + +`tsconfig.json` excludes `**/*.test.ts` so test files never compile into `dist/`. **Any shared test utility that imports `vitest` must also be excluded** — either name it `*.test.ts` or add it to the exclude list (as `testHelpers.ts` is). Otherwise it leaks `vitest` into the published package. + +## n8n integration points + +- All HTTP goes through `this.helpers.httpRequestWithAuthentication.call(this, Cloudinary.CREDENTIAL_TYPE, options)` — do not use `fetch` or `axios`. +- File uploads read binary via `this.helpers.assertBinaryData` + `getBinaryDataBuffer`; the `file` parameter holds the *binary property name*, not the file itself. +- Error handling respects `this.continueOnFail()` — per-item errors are pushed as `{ error: error.message }` rather than thrown when enabled. + +## Build output contract + +`package.json`'s `n8n.nodes` and `n8n.credentials` point at `dist/...` paths. The icon copy in [gulpfile.js](../gulpfile.js) is required because `tsc` won't copy the `.svg`; without it the node loads without an icon in n8n. diff --git a/docs/backward-compatibility-check/README.md b/docs/backward-compatibility-check/README.md new file mode 100644 index 0000000..47d143a --- /dev/null +++ b/docs/backward-compatibility-check/README.md @@ -0,0 +1,86 @@ +# Backward-compatibility verification: `feat/image-video-transformations` vs deployed `0.0.9` + +## What we're verifying and why + +n8n persists every saved workflow as JSON that references this node — and many things *inside* it — by **string identifier** (node `type`, credential `name`, parameter `name`, option `value`). Those strings, plus the values used to *interpret* stored data (`type`, `default`, `displayOptions.show`), form a public contract. See [../backwards-compat.md](../backwards-compat.md) for the full rules. A break means a previously-saved user workflow silently misbehaves or loses a stored value after they upgrade. + +### The baseline (verified, not assumed) + +| Fact | Value | +|---|---| +| Deployed version on npm | **0.0.9** (`npm view n8n-nodes-cloudinary version`), published 2026-01-11 | +| Git commit for 0.0.9 | **`origin/master` = `fbdfa17`** (also the merge-base of this branch) | +| Deployed node descriptor `version` | **1** | +| This branch's descriptor `version` | **still 1** ← key risk | + +Because the descriptor `version` did **not** change while the entire operation/parameter surface was rewritten, **every change must be additive**. Anything non-additive needs either a redesign to be additive, or a `typeVersion` bump in `Cloudinary.node.ts`. + +`origin/master` (`fbdfa17`) is the one true baseline. We do **not** compare against local `master` history beyond it. + +## The automated check + +Two scripts under `docs/backward-compatibility-check/`: + +1. **`extract-contract.cjs`** — instantiates the compiled node (`new Cloudinary().description`) and walks `properties` recursively (into `collection` / `fixedCollection` children) to emit a normalized JSON snapshot of the contract: every parameter `name`+`type`+`default`+`displayOptions`, and every `options[].value`. Run against the built `dist/` of any checkout. +2. **`diff-contract.cjs`** — loads two snapshots (baseline, candidate) and reports **breaking changes** by the rules in `backwards-compat.md`: + - removed/renamed parameter `name` (frozen-by-string) + - removed/renamed option `value` (frozen-by-string) + - changed parameter `type` (frozen-by-meaning) + - changed `default` (frozen-by-meaning) + - narrowed `displayOptions.show` (frozen-by-meaning) + - changed node `type` name or credential `name` + - It also lists **additive** changes (new params/options) as informational. + +Exit code is non-zero if any breaking change is found, so it can gate CI. + +## Run it + +From the repo root: + +```bash +npm run backward-compatibility-check # alias for: bash docs/backward-compatibility-check/run.sh +``` + +This also runs in CI as a required PR gate — see the **Backward-compat contract check** step in [`.github/workflows/pr-check.yml`](../../.github/workflows/pr-check.yml). That checkout uses `fetch-depth: 0` so the baseline commit's history is available for the worktree build. + +`run.sh` does the whole thing hermetically: +- creates a git worktree at the baseline commit (`fbdfa17`) under a temp dir, +- `npm ci && npm run build` there, +- builds the current checkout (`npm run build`), +- extracts both contracts, +- diffs them and prints a verdict, +- cleans up the worktree. + +The verdict is also written to **`.local/backward-compatibility-check-findings.md`** (with the two contract snapshots beside it as `.local/{baseline,candidate}.snapshot.json`). `.local/` is gitignored — these are regenerable, point-in-time artifacts tied to a specific baseline/candidate pair, so they are deliberately **not** committed. Re-run `run.sh` to refresh them; don't hand-edit. + +To run the pieces manually (e.g. against an arbitrary ref): + +```bash +# baseline snapshot +git worktree add /tmp/cld-base fbdfa17 +( cd /tmp/cld-base && npm ci && npm run build ) +node docs/backward-compatibility-check/extract-contract.cjs /tmp/cld-base/dist > /tmp/base.json + +# candidate snapshot (current branch, already built) +npm run build +node docs/backward-compatibility-check/extract-contract.cjs ./dist > /tmp/cand.json + +# diff +node docs/backward-compatibility-check/diff-contract.cjs /tmp/base.json /tmp/cand.json + +git worktree remove /tmp/cld-base --force +``` + +## What the automation can't fully judge (manual review) + +The script flags suspected breaks; a human confirms intent on: + +- **`displayOptions` narrowing** — the script detects when a param is shown in *fewer* `resource`/`operation` combinations than before. Sometimes that's intentional (the op was genuinely removed). Confirm the op still exists. +- **Semantic default changes that aren't literal** — e.g. a field whose effective default comes from handler code, not the descriptor `default`. Spot-check handlers in `operations/` for any field that previously had an implicit behavior. +- **Removed operations** — if an `operation` option `value` from 0.0.9 is gone, every workflow using it orphans. The script flags this; decide whether to restore it or bump `typeVersion`. +- **Credential field changes** — `CloudinaryApi.credentials.ts` gained 18 lines (`privateCdn`/`secureDistribution`). New optional fields are additive; confirm no existing field `name` changed and no existing default flipped. +- **Metadata/tags serialization** — structured metadata must still go through `metadataToPipeString` (pipe-separated, not JSON). A workflow that stored values relies on identical serialization. Covered by unit tests (`cloudinary.utils.test.ts`); confirm those pass. + +## Local end-to-end test (real n8n) + +Automated contract diffing proves the JSON contract holds. To prove a *real saved workflow* still runs, load both versions into a local n8n and replay a workflow built on 0.0.9. See [local-n8n-test.md](local-n8n-test.md). diff --git a/docs/backward-compatibility-check/diff-contract.cjs b/docs/backward-compatibility-check/diff-contract.cjs new file mode 100644 index 0000000..c8d8bc0 --- /dev/null +++ b/docs/backward-compatibility-check/diff-contract.cjs @@ -0,0 +1,148 @@ +#!/usr/bin/env node +/** + * Diff two contract snapshots (from extract-contract.cjs) and report backward- + * compatibility breaks per docs/backwards-compat.md. + * + * Usage: node diff-contract.cjs + * + * Exit code 1 if any BREAKING change is found, else 0 — so it can gate CI. + * + * Breaking (frozen-by-string / frozen-by-meaning): + * - node `name` or `version` changed; credential `name` changed + * - parameter present in baseline missing in candidate (removed/renamed) + * - option `value` present in baseline missing in candidate + * - parameter `type` changed + * - parameter `default` changed + * - displayOptions.show narrowed (param shown in fewer resource/op combos) + * Additive (safe, informational): + * - new parameters, new option values, loosened displayOptions + */ +'use strict'; + +const fs = require('fs'); + +const [, , basePath, candPath] = process.argv; +if (!basePath || !candPath) { + console.error('usage: diff-contract.cjs '); + process.exit(2); +} + +const base = JSON.parse(fs.readFileSync(basePath, 'utf8')); +const cand = JSON.parse(fs.readFileSync(candPath, 'utf8')); + +const breaks = []; +const additions = []; +const warnings = []; + +function indexByPath(snapshot, key) { + const m = new Map(); + for (const p of snapshot[key]) m.set(p.path, p); + return m; +} + +// --- node + credential identity (frozen-by-string) --- +if (base.node.name !== cand.node.name) { + breaks.push(`Node \`name\` changed: "${base.node.name}" -> "${cand.node.name}" (orphans every saved workflow)`); +} +if (base.credential.name !== cand.credential.name) { + breaks.push(`Credential \`name\` changed: "${base.credential.name}" -> "${cand.credential.name}" (orphans stored credentials)`); +} +if (base.node.version !== cand.node.version) { + warnings.push(`Node descriptor \`version\` changed: ${base.node.version} -> ${cand.node.version}. A bump is the sanctioned escape hatch for non-additive changes — confirm the handler branches on typeVersion where semantics diverge.`); +} else { + warnings.push(`Node descriptor \`version\` unchanged (${base.node.version}). EVERY change below must be additive, or it is a break.`); +} + +/** + * displayOptions narrowing check. The relevant axis is `show` on + * resource/operation: a stored value stops being read if the field is shown in + * a strict subset of the combos it used to appear in. + * We approximate "combos" as the cartesian set of show.resource x show.operation. + */ +function showCombos(displayOptions) { + const show = displayOptions && displayOptions.show; + if (!show) return null; // null = "always shown" + const resources = show.resource || ['*']; + const operations = show.operation || ['*']; + const combos = new Set(); + for (const r of resources) for (const o of operations) combos.add(`${r}|${o}`); + return combos; +} + +function compareParams(baseParams, candParams, label) { + const baseIdx = indexByPath({ x: baseParams }, 'x'); + const candIdx = indexByPath({ x: candParams }, 'x'); + + for (const [path, bp] of baseIdx) { + const cp = candIdx.get(path); + if (!cp) { + breaks.push(`[${label}] parameter removed/renamed: \`${path}\` (frozen-by-string)`); + continue; + } + if (bp.type !== cp.type) { + breaks.push(`[${label}] \`${path}\` type changed: "${bp.type}" -> "${cp.type}" (frozen-by-meaning, mis-deserializes stored value)`); + } + if (JSON.stringify(bp.default) !== JSON.stringify(cp.default)) { + breaks.push(`[${label}] \`${path}\` default changed: ${JSON.stringify(bp.default)} -> ${JSON.stringify(cp.default)} (frozen-by-meaning, retroactively rewrites untouched fields)`); + } + // option value set: removed values are breaks; added are additive. + if (bp.optionValues) { + const candVals = new Set(cp.optionValues || []); + for (const v of bp.optionValues) { + if (!candVals.has(v)) { + breaks.push(`[${label}] \`${path}\` option value removed: "${v}" (frozen-by-string, orphans workflows that selected it)`); + } + } + const baseVals = new Set(bp.optionValues); + for (const v of cp.optionValues || []) { + if (!baseVals.has(v)) additions.push(`[${label}] \`${path}\` new option value: "${v}"`); + } + } + // displayOptions narrowing + const baseCombos = showCombos(bp.displayOptions); + const candCombos = showCombos(cp.displayOptions); + if (baseCombos === null && candCombos !== null) { + breaks.push(`[${label}] \`${path}\` displayOptions narrowed: was always-shown, now gated on ${[...candCombos].join(', ')} (frozen-by-meaning, drops stored intent)`); + } else if (baseCombos && candCombos) { + const dropped = [...baseCombos].filter((c) => !candCombos.has(c) && !candCombos.has(c.replace(/\|.*/, '|*'))); + if (dropped.length) { + warnings.push(`[${label}] \`${path}\` may be narrowed: combos no longer shown: ${dropped.join(', ')} — confirm those resource/operation pairs still exist (if the op was removed, that removal is the real break).`); + } + } + } + + for (const [path] of candIdx) { + if (!baseIdx.has(path)) additions.push(`[${label}] new parameter: \`${path}\``); + } +} + +compareParams(base.params, cand.params, 'node'); +compareParams(base.credParams, cand.credParams, 'credential'); + +// --- report --- +const line = '─'.repeat(72); +console.log(line); +console.log('BACKWARD-COMPATIBILITY CONTRACT DIFF'); +console.log(` baseline: ${basePath}`); +console.log(` candidate: ${candPath}`); +console.log(line); + +if (warnings.length) { + console.log('\nNOTES / MANUAL REVIEW:'); + for (const w of warnings) console.log(` • ${w}`); +} + +console.log(`\nADDITIVE changes (safe): ${additions.length}`); +for (const a of additions) console.log(` + ${a}`); + +console.log(`\nBREAKING changes: ${breaks.length}`); +for (const b of breaks) console.log(` ✗ ${b}`); + +console.log('\n' + line); +if (breaks.length) { + console.log(`VERDICT: ${breaks.length} BREAKING change(s) — NOT backward compatible without a typeVersion bump or redesign.`); + process.exit(1); +} else { + console.log('VERDICT: No contract breaks detected. Changes are additive (review NOTES above to confirm).'); + process.exit(0); +} diff --git a/docs/backward-compatibility-check/extract-contract.cjs b/docs/backward-compatibility-check/extract-contract.cjs new file mode 100644 index 0000000..38da9f3 --- /dev/null +++ b/docs/backward-compatibility-check/extract-contract.cjs @@ -0,0 +1,108 @@ +#!/usr/bin/env node +/** + * Extract the n8n public contract from a built Cloudinary node. + * + * Usage: node extract-contract.cjs + * Prints a normalized JSON snapshot to stdout. + * + * The contract = everything n8n persists by string in saved-workflow JSON, plus + * the values it uses to interpret stored data. See docs/backwards-compat.md. + * + * We instantiate the real compiled node and walk description.properties so the + * snapshot reflects exactly what n8n loads — no fragile source parsing. + */ +'use strict'; + +const path = require('path'); + +const distRoot = process.argv[2]; +if (!distRoot) { + console.error('usage: extract-contract.cjs '); + process.exit(2); +} + +const nodePath = path.resolve(distRoot, 'nodes/Cloudinary/Cloudinary.node.js'); +const credPath = path.resolve(distRoot, 'credentials/CloudinaryApi.credentials.js'); + +const nodeMod = require(nodePath); +const NodeClass = nodeMod.Cloudinary || nodeMod.default || Object.values(nodeMod)[0]; +const node = new NodeClass(); +const desc = node.description; + +let credName; +let credProps = []; +try { + const credMod = require(credPath); + const CredClass = credMod.CloudinaryApi || credMod.default || Object.values(credMod)[0]; + const cred = new CredClass(); + credName = cred.name; + credProps = cred.properties || []; +} catch (e) { + credName = ''; +} + +/** + * Walk a properties array recursively. Path is a list of parameter `name`s from + * the root, so collection/fixedCollection children are namespaced and can't + * collide with top-level params of the same name. + */ +function walk(properties, parentPath, out) { + for (const p of properties || []) { + const myPath = [...parentPath, p.name].join('.'); + const entry = { + path: myPath, + name: p.name, + type: p.type, + // `default` is frozen-by-meaning. Normalize undefined to a sentinel so + // "had no default" vs "default is undefined" don't read as a change. + default: 'default' in p ? p.default : '', + // displayOptions.show is the narrowing axis. Capture it verbatim. + displayOptions: p.displayOptions || null, + }; + + // option/multiOptions value sets are frozen-by-string. + if (Array.isArray(p.options) && (p.type === 'options' || p.type === 'multiOptions')) { + entry.optionValues = p.options + .map((o) => o.value) + .sort(); + } + out.params.push(entry); + + // Recurse into collection / fixedCollection children. + if (p.type === 'collection' && Array.isArray(p.options)) { + walk(p.options, [...parentPath, p.name], out); + } + if (p.type === 'fixedCollection' && Array.isArray(p.options)) { + for (const optionGroup of p.options) { + // fixedCollection groups have their own `name` + `values[]`. + walk(optionGroup.values || [], [...parentPath, p.name, optionGroup.name], out); + } + } + } +} + +const out = { + node: { + // frozen-by-string node identity + name: desc.name, + version: desc.version, + }, + credential: { + name: credName, + }, + params: [], + credParams: [], +}; + +walk(desc.properties, [], out); + +// credential properties use the same shape +const credOut = { params: [] }; +walk(credProps, [], credOut); +out.credParams = credOut.params; + +// stable ordering for clean diffs +out.params.sort((a, b) => a.path.localeCompare(b.path)); +out.credParams.sort((a, b) => a.path.localeCompare(b.path)); + +process.stdout.write(JSON.stringify(out, null, 2) + '\n'); diff --git a/docs/backward-compatibility-check/local-n8n-test.md b/docs/backward-compatibility-check/local-n8n-test.md new file mode 100644 index 0000000..c892d51 --- /dev/null +++ b/docs/backward-compatibility-check/local-n8n-test.md @@ -0,0 +1,72 @@ +# Local end-to-end test in a real n8n + +The contract diff proves the saved-workflow JSON contract holds. This proves a *real* workflow built on 0.0.9 still loads and runs after upgrading to the branch — the only way to catch breaks that live in handler code rather than the descriptor. + +## 0. One-time setup + +```bash +npm i -g n8n # or use npx n8n +``` + +## 1. Capture a baseline workflow on deployed 0.0.9 + +Run n8n against the **published** package so you're authoring on the exact version users have: + +```bash +mkdir -p /tmp/n8n-baseline && cd /tmp/n8n-baseline +npm init -y >/dev/null +npm i n8n-nodes-cloudinary@0.0.9 +# point n8n at this folder's node_modules as a custom-extension dir +N8N_CUSTOM_EXTENSIONS="$PWD/node_modules/n8n-nodes-cloudinary" n8n start +``` + +Open http://localhost:5678, add a **Cloudinary** node, and build one workflow per legacy surface (these are the operations that existed in 0.0.9): + +- **Upload → Upload from URL** and **Upload from File** +- **Update Asset → Update Tags** (set a `publicId`, `type`, tags, and something under Update Options) +- **Update Asset → Update Metadata** +- **Admin → Get Tags** and **Get Metadata Fields** + +Configure real values, **execute each once** to confirm it works, then **Download** each workflow to JSON (⋯ menu → Download). Save them under `docs/backward-compatibility-check/fixtures/` (gitignore real credentials — export with credentials *omitted*, n8n does this by default). + +Stop n8n (Ctrl-C). + +## 2. Build and link the branch + +```bash +cd /Users/eitanpeer/dev/n8n-nodes-cloudinary +npm run build +npm link # registers n8n-nodes-cloudinary -> this checkout +``` + +## 3. Load the branch into a fresh n8n and import the 0.0.9 workflows + +```bash +mkdir -p /tmp/n8n-branch && cd /tmp/n8n-branch +npm init -y >/dev/null +npm link n8n-nodes-cloudinary # symlink to the built branch +N8N_CUSTOM_EXTENSIONS="$PWD/node_modules/n8n-nodes-cloudinary" n8n start +``` + +In the UI: **Import from File** each JSON you saved in step 1. + +### What to verify on each imported workflow + +- It loads with **no "unrecognized node/parameter" warnings**. +- Every field you set in 0.0.9 still shows its **stored value** (esp. `publicId`, `type`, tags, Update Options on Update Tags — the fields now gated behind `tagMode`). +- The node still resolves the same **operation** (no "operation not found"). +- **Execute** each — output JSON shape matches what you got in step 1. + +If a field that you set in 0.0.9 now appears empty or hidden, that's a narrowing break — capture it and reconcile against `backwards-compat.md`. + +## 4. (Optional) Credential carry-over + +Re-enter Cloudinary credentials in the branch instance. Confirm the existing `cloudName` / `apiKey` / `apiSecret` fields are unchanged and the two new fields (`privateCdn`, `secureDistribution`) are **optional** and default to off — an existing credential should keep working without touching them. + +## Cleanup + +```bash +npm rm -g n8n-nodes-cloudinary 2>/dev/null +cd /Users/eitanpeer/dev/n8n-nodes-cloudinary && npm unlink 2>/dev/null +rm -rf /tmp/n8n-baseline /tmp/n8n-branch +``` diff --git a/docs/backward-compatibility-check/run.sh b/docs/backward-compatibility-check/run.sh new file mode 100755 index 0000000..4c0e99e --- /dev/null +++ b/docs/backward-compatibility-check/run.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Hermetic backward-compat check: deployed 0.0.9 (origin/master = fbdfa17) vs current checkout. +# Builds the baseline in an isolated worktree, builds the current tree, extracts both +# contracts, and diffs them. Exits non-zero on any breaking change. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +# The commit that produced the published 0.0.9. Override with BASELINE_REF= if needed. +BASELINE_REF="${BASELINE_REF:-fbdfa17}" + +WORKDIR="$(mktemp -d -t cld-compat-XXXXXX)" +BASE_TREE="$WORKDIR/baseline" +OUT="$WORKDIR/out" +mkdir -p "$OUT" + +cleanup() { + git worktree remove "$BASE_TREE" --force >/dev/null 2>&1 || true + rm -rf "$WORKDIR" +} +trap cleanup EXIT + +echo "==> baseline ref: $BASELINE_REF ($(git log -1 --format='%h %s' "$BASELINE_REF"))" +echo "==> candidate: $(git rev-parse --abbrev-ref HEAD) ($(git log -1 --format='%h %s' HEAD))" + +echo "==> building baseline in worktree $BASE_TREE" +git worktree add --quiet --detach "$BASE_TREE" "$BASELINE_REF" +( cd "$BASE_TREE" && npm ci --silent && npm run build --silent ) + +echo "==> building current checkout" +npm run build --silent + +echo "==> extracting contracts" +node "$REPO_ROOT/docs/backward-compatibility-check/extract-contract.cjs" "$BASE_TREE/dist" > "$OUT/baseline.json" +node "$REPO_ROOT/docs/backward-compatibility-check/extract-contract.cjs" "$REPO_ROOT/dist" > "$OUT/candidate.json" + +# Generated report + snapshots go to the gitignored scratch dir — they are +# point-in-time output (true only for this baseline/candidate pair), not source. +SCRATCH="$REPO_ROOT/.local" +mkdir -p "$SCRATCH" +REPORT="$SCRATCH/backward-compatibility-check-findings.md" + +echo "==> diffing" +set +e +DIFF_OUT="$(node "$REPO_ROOT/docs/backward-compatibility-check/diff-contract.cjs" "$OUT/baseline.json" "$OUT/candidate.json")" +RC=$? +set -e + +echo "$DIFF_OUT" + +# Snapshots for inspection. +cp "$OUT/baseline.json" "$SCRATCH/baseline.snapshot.json" +cp "$OUT/candidate.json" "$SCRATCH/candidate.snapshot.json" + +# Write a self-describing report capturing exactly what was compared. +BASE_DESC="$(git log -1 --format='%h %s' "$BASELINE_REF")" +CAND_REF="$(git rev-parse --abbrev-ref HEAD)" +CAND_DESC="$(git log -1 --format='%h %s' HEAD)" +{ + echo "# Backward-compatibility check findings" + echo + echo "> Generated by \`docs/backward-compatibility-check/run.sh\`. Point-in-time output — regenerate, don't edit." + echo + echo "- **Baseline:** \`$BASELINE_REF\` ($BASE_DESC)" + echo "- **Candidate:** \`$CAND_REF\` ($CAND_DESC)" + echo "- **Snapshots:** \`.local/{baseline,candidate}.snapshot.json\`" + echo + echo '```' + echo "$DIFF_OUT" + echo '```' +} > "$REPORT" + +echo "==> report saved to .local/backward-compatibility-check-findings.md" +echo "==> snapshots saved to .local/{baseline,candidate}.snapshot.json" + +exit $RC diff --git a/docs/backwards-compat.md b/docs/backwards-compat.md new file mode 100644 index 0000000..255b76b --- /dev/null +++ b/docs/backwards-compat.md @@ -0,0 +1,23 @@ +# Backwards compatibility (n8n workflow persistence) + +n8n persists each saved workflow as JSON. That JSON references this node — and many things *inside* this node — by string identifier. Anything it references by string is a public API; anything it uses to interpret missing or stored values is part of the same contract. Both can only evolve **additively**, or behind an explicit `typeVersion` bump. + +**Frozen-by-string** — renaming or removing orphans every saved workflow that references it: +- Node `type` (`n8n-nodes-cloudinary.cloudinary`) — class export and top-level `name`. +- Credential `name` (`cloudinaryApi`, exposed via `CREDENTIAL_TYPE`). +- Parameter `name` (every entry under `properties`, at every nesting level — `collection` / `fixedCollection` children included). +- Option `value` in `options` / `multiOptions` — including the `resource` and `operation` selectors, whose `value` strings key the `operationHandlers` map. Renaming `upload:uploadFile` is the same severity of break as renaming a parameter `name`. + +**Frozen-by-meaning** — silent behavior change in saved workflows, no error: +- Parameter `type` (`string` → `number`, `options` → `multiOptions`, `collection` → `fixedCollection`, etc.) — mis-deserializes the stored value. +- `default` — workflows saved before a field existed (or where the user left it untouched) fall back to it on load; changing it retroactively rewrites their behavior. +- The set of `options[].value` — adding entries is safe; removing or renaming one orphans workflows that selected it. +- `displayOptions.show` — *loosening* (showing the field in more cases) is safe; *narrowing* silently drops user intent, since the stored value lingers in the JSON but stops being read. + +**Free to change** — UI-only metadata: `displayName`, `description`, `placeholder`, `hint`, `group`, `icon`, ordering, help text. + +**Additive-mode pattern (preferred).** Add a mode selector (`options`) whose default reproduces current behavior, gate existing fields behind that default, gate new fields behind the new mode, and add a *new* helper for the new path rather than changing the existing one. Worked example — supporting Update Asset by **asset ID** alongside the existing `resourceType` + `type` + `publicId`: the endpoint differs in method as well as path (`PUT /resources/:asset_id` vs `POST /resources/:resource_type/:type/:public_id`), so add `buildResourceUpdateByAssetIdUrl`, an "Identify By" selector defaulting to `Public ID`, an `assetId` field gated on the new mode, and branch the handler. Old workflows (no stored value) get the default and behave exactly as before — no version bump needed. + +**When additive isn't possible, bump `typeVersion`.** Increment `version` in `INodeTypeDescription`, keep the old behavior reachable, and branch on `this.getNode().typeVersion` where semantics diverge. Use this for type changes, removed operations, or any repurposed field. + +**Separate axis — runtime-host compatibility.** `engines.node`, the peer range on `n8n-workflow`, and any native deps govern which n8n installations can *load* this node. That's distinct from workflow-JSON compatibility (which saved workflows still *run* correctly inside a given install). Same discipline — don't silently raise the floor — but it lives in `package.json`, not the node description. diff --git a/docs/conventions.md b/docs/conventions.md new file mode 100644 index 0000000..ad71d1c --- /dev/null +++ b/docs/conventions.md @@ -0,0 +1,16 @@ +# Conventions + +## Naming: mirror the Cloudinary API + +When a field, option, or output key corresponds to something the Cloudinary API already names, **use the API's property name verbatim** — `type` (not "Delivery Type"), `public_id`, `resource_type`, `format`, `version`, `tags`, `context`. The `displayName` should match too (`Type`, not `Delivery Type`). This keeps the node's inputs/outputs interchangeable with API responses (so a Search result pipes straight into a later op) and consistent with the existing `asset`/`updateAsset` fields, which already share `name: 'type'` across resources gated by `displayOptions`. Only invent a name (optionally `transform`-/op-prefixed) for a knob that has **no** API counterpart. Don't prefix or rephrase an API-named field for novelty. + +## Structured metadata format + +Cloudinary expects structured metadata as a pipe-separated `key=value|key=value` string, not JSON. Both upload paths and `updateMetadata` convert the user's JSON input via the shared `metadataToPipeString` helper in [cloudinary.utils.ts](../nodes/Cloudinary/cloudinary.utils.ts) (arrays get `JSON.stringify`ed, scalars passed through). If you add another op that takes structured metadata, call the same helper — passing raw JSON will silently produce a bad request. + +## Codex file (`Cloudinary.node.json`) + +[Cloudinary.node.json](../nodes/Cloudinary/Cloudinary.node.json) is the n8n **codex** — UI metadata for the nodes panel (categories, search aliases, doc links). Format reference: . + +- `categories` must come from n8n's **fixed** list; arbitrary strings (e.g. a former `"Media"`) won't match any UI filter. As of writing the valid values are: Analytics, Communication, Data & Storage, Development, Finance & Accounting, Marketing & Content, Miscellaneous, Productivity, Sales, Utility. We use `Data & Storage` (asset storage) + `Marketing & Content` (closest match for a media/content tool — the list has no "Media"). +- `alias` is a real, working field (extra keywords that surface the node in panel search) but is **undocumented** in the codex reference above — its only authoritative source is the n8n core source. Treat it as best-effort discoverability, not a guaranteed contract. diff --git a/docs/transforms.md b/docs/transforms.md new file mode 100644 index 0000000..73d6194 --- /dev/null +++ b/docs/transforms.md @@ -0,0 +1,16 @@ +# Transform components: keep Multi-Step in sync (reuse the builders) + +The `transform.multiStep` operation (n8n's Edit Image "Multi Step" pattern: a sortable `transformSteps` fixedCollection where each step contributes one or more transformation components, chained with `/` and applied in order) is a **second consumer** of the same transformation logic the standalone transform ops use. To stop the two from drifting, the per-transform logic lives in pure **component builders** in [operations/transform/shared.ts](../nodes/Cloudinary/operations/transform/shared.ts) — `resizeComponents`, `cropComponents`, `trimComponents`, `optimizeComponents`, `convertComponents` — each `(params) => string[]`, throwing a plain `Error` on invalid input. Both call sites map their differently-named fields to the builder's param shape and call it: the standalone op (e.g. [resizeImage.ts](../nodes/Cloudinary/operations/transform/resizeImage.ts)) reads `resizeWidth`/`resizeHeight`/`resizeFit`; the matching Multi-Step step reads the collection's `width`/`height`/`fit` (resize), `cropWidth`/`cropHeight`/`cropAspectWidth`/`cropMode` (crop), etc. `buildComponents(ctx, i, build, prefix?)` wraps a builder call, turning its `Error` into the `NodeOperationError` (with `itemIndex`, and a `Step N: ` prefix for Multi-Step) the handlers throw. + +**When you add or change a transformation, change the builder — not a handler.** Then: (1) if it's a new transform type, add a standalone op AND a `stepType` case in [multiStep.ts](../nodes/Cloudinary/operations/transform/multiStep.ts) + a `Step` field gated on that `stepType` in [transform.fields.ts](../nodes/Cloudinary/descriptions/transform.fields.ts); (2) cover both the standalone op and the Multi-Step step in [transform.test.ts](../nodes/Cloudinary/operations/transform/transform.test.ts). Note builders return a **component list**, so `optimizeComponents` yields `['f_auto', 'q_auto']` → `f_auto/q_auto` (two chained components) in both consumers. The Multi-Step `stepType` field is deliberately **not** named `action`/`operation` — the n8n linter treats those names as operation selectors and demands an `action` property on every option. + +**Field definitions are necessarily split across two UI surfaces — this is the expected maintenance cost.** The transformation *logic* has one home (the builders). The *field declarations* have two: + +| Surface | Location in `transform.fields.ts` | `displayOptions` key | +|---|---|---| +| Standalone op | Top-level `INodeProperties[]`, one block per op | `operation: ['resizeImage']` etc. | +| Multi-Step step | Inside `transformSteps` fixedCollection `values` | `stepType: ['resize']` etc. | + +n8n's `fixedCollection` values don't support referencing external field definitions — everything must be inlined. This means when a transformation gains a new field, you update it in both places. Shared option arrays (e.g. `QUALITY_OPTIONS`, `FIT_OPTIONS`) are extracted as constants in `transform.fields.ts` and reused in both surfaces to reduce copy-paste risk. **If you add a new option array, do the same.** + +Field names within Multi-Step steps differ from their standalone counterparts to avoid collision when multiple step types share the same fixedCollection values object: resize uses `width`/`height`/`fit`; crop uses `cropMode`/`cropWidth`/`cropHeight` (dimensions) or `cropAspectWidth`/`aspectRatio` (aspect ratio). [multiStep.ts](../nodes/Cloudinary/operations/transform/multiStep.ts) reads these and maps them to the builder's param shape. diff --git a/nodes/Cloudinary/Cloudinary.node.json b/nodes/Cloudinary/Cloudinary.node.json index 20e9723..84b896c 100644 --- a/nodes/Cloudinary/Cloudinary.node.json +++ b/nodes/Cloudinary/Cloudinary.node.json @@ -2,7 +2,8 @@ "node": "n8n-nodes-base.cloudinary", "nodeVersion": "1.0", "codexVersion": "1.0", - "categories": ["Media", "Data & Storage"], + "categories": ["Data & Storage", "Marketing & Content"], + "alias": ["video", "image", "media", "transform", "transcode", "optimize", "resize", "crop", "thumbnail", "upload", "CDN", "asset", "DAM"], "resources": { "credentialDocumentation": [ { @@ -12,6 +13,9 @@ "primaryDocumentation": [ { "url": "https://cloudinary.com/documentation/upload_images" + }, + { + "url": "https://cloudinary.com/documentation/video_best_practices" } ] } diff --git a/nodes/Cloudinary/Cloudinary.node.test.ts b/nodes/Cloudinary/Cloudinary.node.test.ts new file mode 100644 index 0000000..59150fa --- /dev/null +++ b/nodes/Cloudinary/Cloudinary.node.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { Cloudinary } from './Cloudinary.node'; +import { makeCtx } from './operations/testHelpers'; + +const node = new Cloudinary(); + +describe('Cloudinary.execute dispatch', () => { + it('routes resource:operation to the matching handler and wraps results with pairedItem', async () => { + const { ctx, http } = makeCtx({ + params: { resource: 'admin', operation: 'getMetadataFields' }, + }); + http.mockResolvedValue({ fields: [] }); + + const [out] = await node.execute.call(ctx); + + expect(out).toEqual([{ json: { fields: [] }, pairedItem: 0 }]); + expect(http).toHaveBeenCalledTimes(1); + }); + + it('throws on an unsupported resource:operation pair', async () => { + const { ctx } = makeCtx({ params: { resource: 'admin', operation: 'nope' } }); + + await expect(node.execute.call(ctx)).rejects.toThrow('Unsupported operation: admin/nope'); + }); + + it('pushes a per-item error instead of throwing when continueOnFail is true', async () => { + const { ctx, http } = makeCtx({ + params: { resource: 'admin', operation: 'getMetadataFields' }, + continueOnFail: true, + }); + http.mockRejectedValue(new Error('boom')); + + const [out] = await node.execute.call(ctx); + + expect(out).toEqual([{ json: { error: 'boom' }, pairedItem: 0 }]); + }); + + it('processes every input item', async () => { + const { ctx, http } = makeCtx({ + params: { resource: 'admin', operation: 'getMetadataFields' }, + items: [{ json: {} }, { json: {} }, { json: {} }] as IDataObject[] as never, + }); + http.mockResolvedValue({ ok: true }); + + const [out] = await node.execute.call(ctx); + + expect(http).toHaveBeenCalledTimes(3); + expect(out).toHaveLength(3); + expect(out.map((r) => r.pairedItem)).toEqual([0, 1, 2]); + }); +}); diff --git a/nodes/Cloudinary/Cloudinary.node.ts b/nodes/Cloudinary/Cloudinary.node.ts index 39c514e..f265ea4 100644 --- a/nodes/Cloudinary/Cloudinary.node.ts +++ b/nodes/Cloudinary/Cloudinary.node.ts @@ -1,860 +1,73 @@ import { INodeType, INodeTypeDescription, - NodeConnectionType, IExecuteFunctions, - IDataObject, INodeExecutionData, - IHttpRequestOptions, - ApplicationError + NodeOperationError, } from 'n8n-workflow'; -import { generateCloudinarySignature, createMultipartBody } from './cloudinary.utils'; - - +import { cloudinaryProperties } from './descriptions'; +import { operationHandlers } from './operations'; +import { CREDENTIAL_TYPE, CloudinaryCredentials } from './operations/types'; export class Cloudinary implements INodeType { - private static readonly CREDENTIAL_TYPE = 'cloudinaryApi'; - description: INodeTypeDescription = { displayName: 'Cloudinary', name: 'cloudinary', icon: 'file:cloudinary.svg', - group: ['Cloudinary'], + group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Upload to Cloudinary', + // Read by both humans (node tooltip in the editor) and LLMs (tool schema when this + // node is invoked via an AI Agent — see `usableAsTool` below). Keep it concise, + // imperative, and capability-focused so agents pick the right operation. + // https://docs.n8n.io/advanced-ai/examples/understand-tools/ + description: 'Upload images and videos, build optimized transformation and delivery URLs, generate video players, and manage, search, tag, and edit metadata in your Cloudinary media library', + documentationUrl: 'https://cloudinary.com/documentation/n8n_integration', + usableAsTool: true, defaults: { name: 'Cloudinary', }, - inputs: [NodeConnectionType.Main], - outputs: [NodeConnectionType.Main], + inputs: ['main'], + outputs: ['main'], credentials: [ { - name: Cloudinary.CREDENTIAL_TYPE, + name: CREDENTIAL_TYPE, required: true, }, ], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Upload', - value: 'upload', - }, - { - name: 'Update Asset', - value: 'updateAsset', - }, - { - name: 'Admin', - value: 'admin', - }, - ], - default: 'upload', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['upload'], - }, - }, - options: [ - { - name: 'Upload From URL', - value: 'uploadUrl', - description: 'Upload an asset from URL', - action: 'Upload an asset from URL', - }, - { - name: 'Upload File', - value: 'uploadFile', - description: 'Upload an asset from file data', - action: 'Upload an asset from file data', - }, - ], - default: 'uploadUrl', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['updateAsset'], - }, - }, - options: [ - { - name: 'Update Asset Tags', - value: 'updateTags', - description: 'Update tags for an existing asset', - action: 'Update asset tags', - }, - { - name: 'Update Asset Structured Metadata', - value: 'updateMetadata', - description: 'Update structured metadata for an existing asset', - action: 'Update asset structured metadata', - }, - ], - default: 'updateTags', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['admin'], - }, - }, - options: [ - { - name: 'Get Tags', - value: 'getTags', - description: 'Get all tags for a specific resource type', - action: 'Get tags for a resource type', - }, - { - name: 'Get Metadata Fields', - value: 'getMetadataFields', - description: 'Get all metadata fields definitions', - action: 'Get metadata fields definitions', - }, - ], - default: 'getTags', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - default: '', - description: 'URL of the image to upload', - required: true, - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadUrl'], - }, - }, - }, - { - displayName: 'Resource Type', - name: 'resource_type', - type: 'options', - options: [ - { - name: 'Image', - value: 'image', - }, - { - name: 'Video', - value: 'video', - }, - { - name: 'Raw', - value: 'raw', - }, - ], - default: 'image', - description: 'The type of asset to upload', - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadUrl'], - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadUrl'], - }, - }, - default: {}, - options: [ - { - displayName: 'Folder', - name: 'folder', - type: 'string', - default: '', - description: 'Folder name where the asset will be stored', - }, - { - displayName: 'Public ID', - name: 'public_id', - type: 'string', - default: '', - description: 'The public ID of the resource', - }, - { - displayName: 'Structured Metadata', - name: 'metadata', - type: 'json', - default: '{}', - description: 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', - }, - { - displayName: 'Tags', - name: 'tags', - type: 'string', - default: '', - description: 'A comma-separated list of tag names to assign to the asset', - }, - { - displayName: 'Upload Preset', - name: 'upload_preset', - type: 'string', - default: '', - description: 'Name of an upload preset that you defined for your Cloudinary account', - }, - ], - }, - { - displayName: 'Public ID', - name: 'publicId', - type: 'string', - default: '', - description: 'The public ID of the asset to update', - required: true, - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateTags', 'updateMetadata'], - }, - }, - }, - { - displayName: 'Resource Type', - name: 'resourceType', - type: 'options', - options: [ - { - name: 'Image', - value: 'image', - }, - { - name: 'Video', - value: 'video', - }, - { - name: 'Raw', - value: 'raw', - }, - ], - default: 'image', - description: 'The type of asset to update', - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateTags', 'updateMetadata'], - }, - }, - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - options: [ - { - name: 'Upload', - value: 'upload', - }, - { - name: 'Private', - value: 'private', - }, - { - name: 'Authenticated', - value: 'authenticated', - }, - { - name: 'Fetch', - value: 'fetch', - }, - ], - default: 'upload', - description: 'The storage type of the asset', - required: true, - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateTags', 'updateMetadata'], - }, - }, - }, - { - displayName: 'Tags', - name: 'tags', - type: 'string', - default: '', - description: 'A comma-separated list of tag names to assign to the asset', - required: true, - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateTags'], - }, - }, - }, - { - displayName: 'Structured Metadata', - name: 'structuredMetadata', - type: 'json', - default: '{}', - description: 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', - required: true, - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateMetadata'], - }, - }, - }, - { - displayName: 'Resource Type', - name: 'getTagsResourceType', - type: 'options', - options: [ - { - name: 'Image', - value: 'image', - }, - { - name: 'Video', - value: 'video', - }, - { - name: 'Raw', - value: 'raw', - }, - ], - default: 'image', - description: 'The type of resource to get tags for', - required: true, - displayOptions: { - show: { - resource: ['admin'], - operation: ['getTags'], - }, - }, - }, - { - displayName: 'Prefix', - name: 'tagsPrefix', - type: 'string', - default: '', - description: 'Filter tags that start with this prefix', - displayOptions: { - show: { - resource: ['admin'], - operation: ['getTags'], - }, - }, - }, - { - displayName: 'Max Results', - name: 'tagsMaxResults', - type: 'number', - default: 100, - description: 'Maximum number of tags to return (1-500)', - typeOptions: { - minValue: 1, - maxValue: 500, - }, - displayOptions: { - show: { - resource: ['admin'], - operation: ['getTags'], - }, - }, - }, - { - displayName: 'Update Fields', - name: 'updateOptions', - type: 'collection', - placeholder: 'Add Option', - displayOptions: { - show: { - resource: ['updateAsset'], - operation: ['updateTags', 'updateMetadata'], - }, - }, - default: {}, - options: [ - { - displayName: 'Invalidate CDN', - name: 'invalidate', - type: 'boolean', - default: false, - description: 'Whether to invalidate CDN cache copies of the asset', - }, - ], - }, - { - displayName: 'File', - name: 'file', - type: 'string', - typeOptions: { - propertyType: 'binary', - }, - default: 'data', - description: 'The file to upload', - required: true, - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadFile'], - }, - }, - }, - { - displayName: 'Resource Type', - name: 'resource_type_file', - type: 'options', - options: [ - { - name: 'Image', - value: 'image', - }, - { - name: 'Video', - value: 'video', - }, - { - name: 'Raw', - value: 'raw', - }, - ], - default: 'image', - description: 'The type of asset to upload', - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadFile'], - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFieldsFile', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - resource: ['upload'], - operation: ['uploadFile'], - }, - }, - default: {}, - options: [ - { - displayName: 'Folder', - name: 'folder', - type: 'string', - default: '', - description: 'Folder name where the asset will be stored', - }, - { - displayName: 'Public ID', - name: 'public_id', - type: 'string', - default: '', - description: 'The public ID of the resource', - }, - { - displayName: 'Structured Metadata', - name: 'metadata', - type: 'json', - default: '{}', - description: 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', - }, - { - displayName: 'Tags', - name: 'tags', - type: 'string', - default: '', - description: 'A comma-separated list of tag names to assign to the asset', - }, - { - displayName: 'Upload Preset', - name: 'upload_preset', - type: 'string', - default: '', - description: 'Name of an upload preset that you defined for your Cloudinary account', - }, - ], - }, - ], + properties: cloudinaryProperties, }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; - const credentials = await this.getCredentials(Cloudinary.CREDENTIAL_TYPE); - const cloudName = credentials.cloudName as string; - const apiKey = credentials.apiKey as string; - const apiSecret = credentials.apiSecret as string; + const credentials = await this.getCredentials(CREDENTIAL_TYPE); + const creds: CloudinaryCredentials = { + cloudName: credentials.cloudName as string, + apiKey: credentials.apiKey as string, + apiSecret: credentials.apiSecret as string, + privateCdn: (credentials.privateCdn as boolean) ?? false, + secureDistribution: (credentials.secureDistribution as string) ?? '', + }; for (let i = 0; i < items.length; i++) { try { const resource = this.getNodeParameter('resource', i) as string; const operation = this.getNodeParameter('operation', i) as string; - if (resource === 'upload' && operation === 'uploadUrl') { - const url = this.getNodeParameter('url', i) as string; - const resourceType = this.getNodeParameter('resource_type', i) as string; - const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject; - - // Process metadata if provided - if (additionalFields.metadata) { - let metadata: IDataObject; - try { - metadata = typeof additionalFields.metadata === 'object' - ? additionalFields.metadata as IDataObject - : JSON.parse(additionalFields.metadata as string); - } catch (error) { - throw new ApplicationError('Invalid JSON for structured metadata'); - } - // Convert metadata object to pipe-separated string format expected by Cloudinary - const metadataString = Object.keys(metadata) - .map((key) => { - const value = metadata[key]; - const formattedValue = Array.isArray(value) ? JSON.stringify(value) : value; - return `${key}=${formattedValue}`; - }) - .join('|'); - additionalFields.metadata = metadataString; - } - - // Build parameters for the upload - const timestamp = Math.round(new Date().getTime() / 1000); - const params: IDataObject = { - timestamp, - api_key: apiKey, - file: url, - ...additionalFields, - }; - - // Generate signature - const signature = generateCloudinarySignature(params, apiSecret); - params.signature = signature; - - // Prepare the URL for upload - const uploadUrl = `https://api.cloudinary.com/v1_1/${cloudName}/${resourceType}/upload`; - - // Set up the request - const options: IHttpRequestOptions = { - method: 'POST', - url: uploadUrl, - body: params, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'n8n/1.0', - }, - }; - - // Make the API request - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, + const handler = operationHandlers[`${resource}:${operation}`]; + if (!handler) { + throw new NodeOperationError( + this.getNode(), + `Unsupported operation: ${resource}/${operation}`, + { itemIndex: i }, ); - - returnData.push({ - json: response, - pairedItem: i, - }); } - if (resource === 'upload' && operation === 'uploadFile') { - const fileData = this.getNodeParameter('file', i) as string; - const resourceType = this.getNodeParameter('resource_type_file', i) as string; - const additionalFields = this.getNodeParameter( - 'additionalFieldsFile', - i, - {}, - ) as IDataObject; - - // Process metadata if provided - if (additionalFields.metadata) { - let metadata: IDataObject; - try { - metadata = typeof additionalFields.metadata === 'object' - ? additionalFields.metadata as IDataObject - : JSON.parse(additionalFields.metadata as string); - } catch (error) { - throw new ApplicationError('Invalid JSON for structured metadata'); - } - // Convert metadata object to pipe-separated string format expected by Cloudinary - const metadataString = Object.keys(metadata) - .map((key) => { - const value = metadata[key]; - const formattedValue = Array.isArray(value) ? JSON.stringify(value) : value; - return `${key}=${formattedValue}`; - }) - .join('|'); - additionalFields.metadata = metadataString; - } - - // Get the binary data from input - const binaryPropertyName = fileData; - const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); - const dataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); - - // Build parameters for the upload (for signature generation) - const timestamp = Math.round(new Date().getTime() / 1000); - const params: IDataObject = { - timestamp, - api_key: apiKey, - ...additionalFields, - }; - - // Generate signature - const signature = generateCloudinarySignature(params, apiSecret); - - // Prepare the URL for upload - const uploadUrl = `https://api.cloudinary.com/v1_1/${cloudName}/${resourceType}/upload`; - - // Prepare fields for multipart form data - const fields: Record = { - api_key: apiKey, - timestamp: timestamp.toString(), - signature: signature, - }; - - // Add additional fields - for (const key in additionalFields) { - fields[key] = additionalFields[key] as string; - } - - // Create multipart body - const { body, boundary } = createMultipartBody( - fields, - dataBuffer, - binaryData.fileName || 'file', - binaryData.mimeType || 'application/octet-stream' - ); - - // Set up the request - const options: IHttpRequestOptions = { - method: 'POST', - url: uploadUrl, - body: body, - headers: { - 'Content-Type': `multipart/form-data; boundary=${boundary}`, - 'User-Agent': 'n8n/1.0', - }, - }; - - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, - ); - - returnData.push({ - json: response, - pairedItem: i, - }); - } - - if (resource === 'admin' && operation === 'getTags') { - const resourceType = this.getNodeParameter('getTagsResourceType', i) as string; - const prefix = this.getNodeParameter('tagsPrefix', i, '') as string; - const maxResults = this.getNodeParameter('tagsMaxResults', i, 100) as number; - - // Build the URL for tags endpoint - const tagsUrl = `https://api.cloudinary.com/v1_1/${cloudName}/tags/${resourceType}`; - - // Build query parameters - const queryParams: IDataObject = {}; - if (prefix) { - queryParams.prefix = prefix; - } - if (maxResults) { - queryParams.max_results = maxResults; - } - - // Set up the request with basic auth and query parameters - const options: IHttpRequestOptions = { - method: 'GET', - url: tagsUrl, - qs: queryParams, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'n8n/1.0', - }, - auth: { - username: apiKey, - password: apiSecret, - }, - }; - - // Make the API request - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, - ); - - returnData.push({ - json: response, - pairedItem: i, - }); - } - - if (resource === 'admin' && operation === 'getMetadataFields') { - // Build the URL for metadata fields endpoint - const metadataUrl = `https://api.cloudinary.com/v1_1/${cloudName}/metadata_fields`; - - // Set up the request with basic auth - const options: IHttpRequestOptions = { - method: 'GET', - url: metadataUrl, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'n8n/1.0', - }, - auth: { - username: apiKey, - password: apiSecret, - }, - }; - - // Make the API request - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, - ); - - returnData.push({ - json: response, - pairedItem: i, - }); - } - - if (resource === 'updateAsset' && operation === 'updateTags') { - const publicId = this.getNodeParameter('publicId', i) as string; - const resourceType = this.getNodeParameter('resourceType', i) as string; - const type = this.getNodeParameter('type', i) as string; - const tags = this.getNodeParameter('tags', i) as string; - const updateOptions = this.getNodeParameter('updateOptions', i, {}) as IDataObject; - - // Build the request body - const body: IDataObject = { - tags: tags, - ...updateOptions, - }; - - // Prepare the URL for resource update - const updateUrl = `https://api.cloudinary.com/v1_1/${cloudName}/resources/${resourceType}/${type}/${publicId}`; - - // Set up the request with basic auth - const options: IHttpRequestOptions = { - method: 'POST', - url: updateUrl, - body: body, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'n8n/1.0', - }, - auth: { - username: apiKey, - password: apiSecret, - }, - }; - - // Make the API request - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, - ); - - returnData.push({ - json: response, - pairedItem: i, - }); - } - - if (resource === 'updateAsset' && operation === 'updateMetadata') { - const publicId = this.getNodeParameter('publicId', i) as string; - const resourceType = this.getNodeParameter('resourceType', i) as string; - const type = this.getNodeParameter('type', i) as string; - const structuredMetadata = this.getNodeParameter('structuredMetadata', i) as string; - const updateOptions = this.getNodeParameter('updateOptions', i, {}) as IDataObject; - - // Parse structured metadata - let metadata: IDataObject; - try { - metadata = typeof structuredMetadata === 'object' ? structuredMetadata : JSON.parse(structuredMetadata); - } catch (error) { - throw new ApplicationError('Invalid JSON for structured metadata'); - } - - // Convert metadata object to pipe-separated string format expected by Cloudinary - const metadataString = Object.keys(metadata) - .map((key) => { - const value = metadata[key]; - // If value is an array, stringify it; otherwise use as is - const formattedValue = Array.isArray(value) ? JSON.stringify(value) : value; - return `${key}=${formattedValue}`; - }) - .join('|'); - - // Build the request body - const body: IDataObject = { - metadata: metadataString, - ...updateOptions, - }; - - // Prepare the URL for resource update - const updateUrl = `https://api.cloudinary.com/v1_1/${cloudName}/resources/${resourceType}/${type}/${publicId}`; - - // Set up the request with basic auth - const options: IHttpRequestOptions = { - method: 'POST', - url: updateUrl, - body: body, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'n8n/1.0', - }, - auth: { - username: apiKey, - password: apiSecret, - }, - }; - - // Make the API request - const response = await this.helpers.httpRequestWithAuthentication.call( - this, - Cloudinary.CREDENTIAL_TYPE, - options, - ); - - returnData.push({ - json: response, - pairedItem: i, - }); + const results = await handler(this, i, creds); + for (const json of results) { + returnData.push({ json, pairedItem: i }); } } catch (error) { if (this.continueOnFail()) { diff --git a/nodes/Cloudinary/cloudinary.utils.test.ts b/nodes/Cloudinary/cloudinary.utils.test.ts new file mode 100644 index 0000000..117d98a --- /dev/null +++ b/nodes/Cloudinary/cloudinary.utils.test.ts @@ -0,0 +1,572 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { + buildSearchExpression, + createMultipartBody, + extractCloudinaryError, + generateCloudinarySignature, + metadataToPipeString, + buildUploadUrl, + buildResourceByAssetIdUrl, + buildResourceDeleteUrl, + buildResourceUpdateUrl, + buildDeliveryUrl, + joinTransformation, + splitCsvIds, +} from './cloudinary.utils'; + +describe('buildSearchExpression', () => { + it('returns input unchanged when all three resource types are selected', () => { + expect(buildSearchExpression('tags=cat', ['image', 'video', 'raw'])).toBe('tags=cat'); + }); + + it('returns input unchanged when resource types array is empty', () => { + expect(buildSearchExpression('tags=cat', [])).toBe('tags=cat'); + }); + + it('returns just the clause when input is empty and one resource type is selected', () => { + expect(buildSearchExpression('', ['image'])).toBe('resource_type:image'); + }); + + it('wraps the input and ANDs a single resource_type clause', () => { + expect(buildSearchExpression('tags=cat', ['video'])).toBe( + '(tags=cat) AND resource_type:video', + ); + }); + + it('builds an OR-grouped clause for two resource types', () => { + expect(buildSearchExpression('tags=cat', ['image', 'video'])).toBe( + '(tags=cat) AND resource_type:(image OR video)', + ); + }); + + it('skips injection when input already has a resource_type: clause', () => { + expect(buildSearchExpression('resource_type:video AND tags=cat', ['image'])).toBe( + 'resource_type:video AND tags=cat', + ); + }); + + it('skips injection when input already has a resource_type= clause', () => { + expect(buildSearchExpression('resource_type=raw', ['image'])).toBe('resource_type=raw'); + }); + + it('detects resource_type clause case-insensitively', () => { + expect(buildSearchExpression('Resource_Type:video', ['image'])).toBe('Resource_Type:video'); + }); + + it('does not match resource_type as a substring of another field name', () => { + expect(buildSearchExpression('my_resource_type_extra:foo', ['image'])).toBe( + '(my_resource_type_extra:foo) AND resource_type:image', + ); + }); +}); + +// buildSearchExpression does NO syntax validation — it treats the expression as +// opaque text. Malformed input must pass through unchanged (or wrapped verbatim); +// rejecting/repairing it is the Cloudinary server's job, surfaced by the search +// handler's 400 "query error" branch. These guard against someone later adding +// naive validation or escaping here that would silently alter user queries. +describe('buildSearchExpression with malformed expressions', () => { + it('does not throw on any broken expression', () => { + for (const broken of ['(tags=cat', 'tags="open', 'tags=cat AND', 'tags=', 'AND OR ()']) { + expect(() => buildSearchExpression(broken, ['image'])).not.toThrow(); + } + }); + + it('wraps an unbalanced-parenthesis expression verbatim and still appends the clause', () => { + expect(buildSearchExpression('(tags=cat', ['image'])).toBe( + '((tags=cat) AND resource_type:image', + ); + }); + + it('preserves an unclosed quote without escaping or closing it', () => { + expect(buildSearchExpression('tags="back to school', ['video'])).toBe( + '(tags="back to school) AND resource_type:video', + ); + }); + + it('preserves a trailing boolean operator', () => { + expect(buildSearchExpression('tags=cat AND', ['image'])).toBe( + '(tags=cat AND) AND resource_type:image', + ); + }); + + it('wraps an empty-value clause (tags=) rather than dropping it', () => { + expect(buildSearchExpression('tags=', ['image'])).toBe('(tags=) AND resource_type:image'); + }); + + it('passes a broken expression through unchanged when no clause is added (3 types)', () => { + expect(buildSearchExpression('(tags=cat', ['image', 'video', 'raw'])).toBe('(tags=cat'); + }); + + it('passes a broken expression through unchanged when no resource types are selected', () => { + expect(buildSearchExpression('tags="open', [])).toBe('tags="open'); + }); + + it('still skips clause injection when a broken expression contains a resource_type clause', () => { + expect(buildSearchExpression('resource_type:video AND (tags=cat', ['image'])).toBe( + 'resource_type:video AND (tags=cat', + ); + }); +}); + +describe('extractCloudinaryError', () => { + it('reads status from httpCode', () => { + expect(extractCloudinaryError({ httpCode: 400 }).status).toBe(400); + }); + + it('reads status from statusCode when httpCode is missing', () => { + expect(extractCloudinaryError({ statusCode: 420 }).status).toBe(420); + }); + + it('reads status from response.status when both above are missing', () => { + expect(extractCloudinaryError({ response: { status: 429 } }).status).toBe(429); + }); + + it('returns undefined status when nothing provides it', () => { + expect(extractCloudinaryError({}).status).toBeUndefined(); + }); + + it('extracts cloudinaryMessage from response.body.error.message', () => { + const err = { response: { body: { error: { message: 'Query Error (at position 54)' } } } }; + expect(extractCloudinaryError(err).cloudinaryMessage).toBe('Query Error (at position 54)'); + }); + + it('falls back to response.data.error.message', () => { + const err = { response: { data: { error: { message: 'Resource not found' } } } }; + expect(extractCloudinaryError(err).cloudinaryMessage).toBe('Resource not found'); + }); + + it('uses x-cld-error header as last resort', () => { + const err = { response: { headers: { 'x-cld-error': 'Invalid API key' } } }; + expect(extractCloudinaryError(err).cloudinaryMessage).toBe('Invalid API key'); + }); + + it('prefers body.error.message over data.error.message and header', () => { + const err = { + response: { + body: { error: { message: 'from body' } }, + data: { error: { message: 'from data' } }, + headers: { 'x-cld-error': 'from header' }, + }, + }; + expect(extractCloudinaryError(err).cloudinaryMessage).toBe('from body'); + }); + + it('reads retry-after header (lowercase)', () => { + const err = { response: { headers: { 'retry-after': '60' } } }; + expect(extractCloudinaryError(err).retryAfter).toBe('60'); + }); + + it('reads Retry-After header (canonical case)', () => { + const err = { response: { headers: { 'Retry-After': '120' } } }; + expect(extractCloudinaryError(err).retryAfter).toBe('120'); + }); + + it('returns undefined retryAfter when header is absent', () => { + expect(extractCloudinaryError({ response: { headers: {} } }).retryAfter).toBeUndefined(); + }); + + it('returns undefined cloudinaryMessage when no known shape is present', () => { + const err = { response: { body: { unrelated: 'data' } } }; + expect(extractCloudinaryError(err).cloudinaryMessage).toBeUndefined(); + }); + + it('reads headers from cause.response.headers fallback', () => { + const err = { cause: { response: { headers: { 'retry-after': '30' } } } }; + expect(extractCloudinaryError(err).retryAfter).toBe('30'); + }); +}); + +describe('generateCloudinarySignature', () => { + it('excludes signature, api_key, and file fields from the signed payload', () => { + const withExcluded = generateCloudinarySignature( + { timestamp: 1234567890, public_id: 'sample', api_key: 'should-be-ignored', file: 'http://example.com/a.jpg', signature: 'old' }, + 'secret', + ); + const withoutExcluded = generateCloudinarySignature( + { timestamp: 1234567890, public_id: 'sample' }, + 'secret', + ); + expect(withExcluded).toBe(withoutExcluded); + }); + + it('sorts parameters alphabetically (order-independent)', () => { + const a = generateCloudinarySignature({ timestamp: 1, public_id: 'x' }, 'secret'); + const b = generateCloudinarySignature({ public_id: 'x', timestamp: 1 }, 'secret'); + expect(a).toBe(b); + }); + + it('produces a different signature when api_secret changes', () => { + const a = generateCloudinarySignature({ timestamp: 1 }, 'secret-a'); + const b = generateCloudinarySignature({ timestamp: 1 }, 'secret-b'); + expect(a).not.toBe(b); + }); + + it('produces a 64-char hex SHA-256 digest', () => { + const sig = generateCloudinarySignature({ timestamp: 1, public_id: 'x' }, 'secret'); + expect(sig).toMatch(/^[a-f0-9]{64}$/); + }); +}); + +describe('metadataToPipeString', () => { + it('joins scalar key/value pairs with pipes', () => { + expect(metadataToPipeString({ a: '1', b: '2' })).toBe('a=1|b=2'); + }); + + it('renders array values as a bracketed list of quoted strings', () => { + expect(metadataToPipeString({ colors: ['red', 'blue'] })).toBe('colors=["red","blue"]'); + }); + + it('quotes numeric array elements as strings', () => { + expect(metadataToPipeString({ sizes: [1, 2] } as unknown as IDataObject)).toBe( + 'sizes=["1","2"]', + ); + }); + + it('renders an empty array as empty brackets', () => { + expect(metadataToPipeString({ tags: [] })).toBe('tags=[]'); + }); + + it('parses a JSON string input', () => { + expect(metadataToPipeString('{"a":"1","b":"2"}')).toBe('a=1|b=2'); + }); + + it('returns an empty string for an empty object', () => { + expect(metadataToPipeString({})).toBe(''); + }); + + it('throws on invalid JSON string input', () => { + expect(() => metadataToPipeString('{not valid}')).toThrow('Invalid JSON for structured metadata'); + }); + + it('escapes the pipe delimiter in a scalar value', () => { + expect(metadataToPipeString({ note: 'a|b' })).toBe('note=a\\|b'); + }); + + it('escapes the equals delimiter in a scalar value', () => { + expect(metadataToPipeString({ eq: 'x=y' })).toBe('eq=x\\=y'); + }); + + it('escapes both delimiters and keeps following pairs separable', () => { + expect(metadataToPipeString({ a: 'one=two|three', b: 'ok' })).toBe('a=one\\=two\\|three|b=ok'); + }); + + it('escapes double quotes in a scalar value', () => { + expect(metadataToPipeString({ q: 'say "hi"' })).toBe('q=say \\"hi\\"'); + }); + + it('escapes the delimiters (= | ") inside array elements and quote-wraps them', () => { + expect(metadataToPipeString({ tags: ['a|b', 'c=d', 'e"f'] })).toBe( + 'tags=["a\\|b","c\\=d","e\\"f"]', + ); + }); + + it('keeps following pairs separable when an array element contains a pipe', () => { + expect(metadataToPipeString({ a: ['x|y'], b: 'ok' })).toBe('a=["x\\|y"]|b=ok'); + }); + + it('skips null and undefined values rather than emitting key=null', () => { + expect(metadataToPipeString({ a: '1', b: null, c: undefined, d: '2' } as IDataObject)).toBe( + 'a=1|d=2', + ); + }); + + it('stringifies non-string scalars (numbers, booleans)', () => { + expect(metadataToPipeString({ n: 5, flag: true } as unknown as IDataObject)).toBe( + 'n=5|flag=true', + ); + }); +}); + +describe('createMultipartBody', () => { + const decode = (fields: Record, name: string) => + createMultipartBody(fields, Buffer.from('data'), name, 'image/png').body.toString('utf8'); + + it('sets the filename in the Content-Disposition of the file part', () => { + expect(decode({}, 'photo.png')).toContain( + 'Content-Disposition: form-data; name="file"; filename="photo.png"', + ); + }); + + it('strips CR/LF from the filename so it cannot inject extra headers', () => { + const body = decode({}, 'a\r\nContent-Type: text/html\r\n.png'); + expect(body).toContain('filename="aContent-Type: text/html.png"'); + // the injected sequence must not appear as its own header line + expect(body).not.toContain('\r\nContent-Type: text/html\r\n'); + }); + + it('replaces embedded double-quotes so they cannot close the filename attribute', () => { + expect(decode({}, 'a".png')).toContain('filename="a\'.png"'); + }); +}); + +describe('buildUploadUrl', () => { + it('builds the signed-upload endpoint for a cloud and resource type', () => { + expect(buildUploadUrl('demo', 'image')).toBe( + 'https://api.cloudinary.com/v1_1/demo/image/upload', + ); + }); +}); + +describe('buildResourceUpdateUrl', () => { + it('builds the Admin API resource-update URL', () => { + expect(buildResourceUpdateUrl('demo', 'image', 'upload', 'sample')).toBe( + 'https://api.cloudinary.com/v1_1/demo/resources/image/upload/sample', + ); + }); +}); + +describe('buildResourceByAssetIdUrl', () => { + it('builds the Admin API asset_id-keyed resource URL', () => { + expect(buildResourceByAssetIdUrl('demo', 'abc123')).toBe( + 'https://api.cloudinary.com/v1_1/demo/resources/abc123', + ); + }); +}); + +describe('buildResourceDeleteUrl', () => { + it('builds the per-(resource_type, type) bulk-delete URL', () => { + expect(buildResourceDeleteUrl('demo', 'image', 'upload')).toBe( + 'https://api.cloudinary.com/v1_1/demo/resources/image/upload', + ); + }); +}); + +describe('buildDeliveryUrl', () => { + describe('host resolution', () => { + it('uses the shared host with the cloud name in the path by default', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + transformation: 'f_auto/q_auto', + publicId: 'sample', + }), + ).toBe('https://res.cloudinary.com/demo/image/upload/f_auto/q_auto/sample'); + }); + + it('moves the cloud name into the subdomain for a private CDN', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + transformation: 'f_auto/q_auto', + publicId: 'sample', + privateCdn: true, + }), + ).toBe('https://demo-res.cloudinary.com/image/upload/f_auto/q_auto/sample'); + }); + + it('drops the cloud name entirely for a custom hostname (CNAME)', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + transformation: 'f_auto/q_auto', + publicId: 'sample', + secureDistribution: 'assets.example.com', + }), + ).toBe('https://assets.example.com/image/upload/f_auto/q_auto/sample'); + }); + + it('lets a custom hostname win over the private-CDN flag', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + publicId: 'sample', + privateCdn: true, + secureDistribution: 'assets.example.com', + }), + ).toBe('https://assets.example.com/image/upload/sample'); + }); + + it('strips a scheme and trailing slash from a pasted custom hostname', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + publicId: 'sample', + secureDistribution: 'https://assets.example.com/', + }), + ).toBe('https://assets.example.com/image/upload/sample'); + }); + }); + + describe('path composition', () => { + it('omits the transformation segment when none is given', () => { + expect( + buildDeliveryUrl({ cloudName: 'demo', resourceType: 'image', type: 'upload', publicId: 'sample' }), + ).toBe('https://res.cloudinary.com/demo/image/upload/sample'); + }); + + it('omits an empty-string transformation', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'video', + type: 'upload', + transformation: '', + publicId: 'clip', + }), + ).toBe('https://res.cloudinary.com/demo/video/upload/clip'); + }); + + it('appends the format as an extension on the public id', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'video', + type: 'upload', + transformation: 'so_2', + publicId: 'clip', + format: 'jpg', + }), + ).toBe('https://res.cloudinary.com/demo/video/upload/so_2/clip.jpg'); + }); + + it('emits a v-prefixed version segment before the public id', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + publicId: 'sample', + version: 1234567890, + }), + ).toBe('https://res.cloudinary.com/demo/image/upload/v1234567890/sample'); + }); + + it('omits the version segment for an empty-string version', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + publicId: 'sample', + version: '', + }), + ).toBe('https://res.cloudinary.com/demo/image/upload/sample'); + }); + + it('preserves a folder-nested public id verbatim', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + transformation: 'f_auto/q_auto', + publicId: 'folder/sub/sample', + format: 'webp', + }), + ).toBe('https://res.cloudinary.com/demo/image/upload/f_auto/q_auto/folder/sub/sample.webp'); + }); + + it('supports a non-upload delivery type', () => { + expect( + buildDeliveryUrl({ cloudName: 'demo', resourceType: 'image', type: 'fetch', publicId: 'sample' }), + ).toBe('https://res.cloudinary.com/demo/image/fetch/sample'); + }); + }); + + describe('fetch remote-URL escaping', () => { + it('leaves a clean fetch URL readable (scheme/host/path unescaped)', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'fetch', + publicId: 'https://example.com/photos/cat.jpg', + }), + ).toBe('https://res.cloudinary.com/demo/image/fetch/https://example.com/photos/cat.jpg'); + }); + + it('percent-encodes the query string so it reaches Cloudinary as the path', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'fetch', + publicId: 'https://example.com/photo.jpg?sig=abc&w=10', + }), + ).toBe( + 'https://res.cloudinary.com/demo/image/fetch/https://example.com/photo.jpg%3Fsig%3Dabc%26w%3D10', + ); + }); + + it('percent-encodes a fragment so it is not dropped by the browser', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'fetch', + publicId: 'https://example.com/photo.jpg#v1', + }), + ).toBe('https://res.cloudinary.com/demo/image/fetch/https://example.com/photo.jpg%23v1'); + }); + + it('escapes spaces and other unsafe chars in a fetch URL', () => { + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'fetch', + publicId: 'https://example.com/my photo.jpg', + }), + ).toBe('https://res.cloudinary.com/demo/image/fetch/https://example.com/my%20photo.jpg'); + }); + + it('does NOT escape identifiers for non-fetch types', () => { + // A stored public_id with a literal `?` is opaque, not a URL — leave it verbatim. + expect( + buildDeliveryUrl({ + cloudName: 'demo', + resourceType: 'image', + type: 'upload', + publicId: 'weird?name', + }), + ).toBe('https://res.cloudinary.com/demo/image/upload/weird?name'); + }); + }); +}); + +describe('joinTransformation', () => { + it('joins components with slashes', () => { + expect(joinTransformation(['c_fill,g_auto,w_400', 'f_auto', 'q_auto'])).toBe( + 'c_fill,g_auto,w_400/f_auto/q_auto', + ); + }); + + it('drops undefined, null, empty, and whitespace-only components', () => { + expect(joinTransformation(['c_fill,w_400', undefined, '', null, ' ', 'q_auto'])).toBe( + 'c_fill,w_400/q_auto', + ); + }); + + it('trims surrounding whitespace from each component', () => { + expect(joinTransformation([' c_fill,w_400 ', ' q_auto '])).toBe('c_fill,w_400/q_auto'); + }); + + it('returns an empty string when every component is empty', () => { + expect(joinTransformation([undefined, '', null, ' '])).toBe(''); + }); + + it('returns an empty string for an empty array', () => { + expect(joinTransformation([])).toBe(''); + }); +}); + +describe('splitCsvIds', () => { + it('splits, trims, and drops empty entries', () => { + expect(splitCsvIds('a, b ,c')).toEqual(['a', 'b', 'c']); + expect(splitCsvIds(' , a, , b,')).toEqual(['a', 'b']); + expect(splitCsvIds('')).toEqual([]); + expect(splitCsvIds('solo')).toEqual(['solo']); + }); +}); diff --git a/nodes/Cloudinary/cloudinary.utils.ts b/nodes/Cloudinary/cloudinary.utils.ts index 378a990..dfdb70d 100644 --- a/nodes/Cloudinary/cloudinary.utils.ts +++ b/nodes/Cloudinary/cloudinary.utils.ts @@ -1,5 +1,221 @@ -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, ApplicationError } from 'n8n-workflow'; import { sha256 } from './sha256.utils'; +import { CloudinaryCredentials } from './operations/types'; +import { version } from '../../package.json'; + +const CLOUDINARY_API_BASE = 'https://api.cloudinary.com/v1_1'; + +/** User-Agent sent on every Cloudinary request. */ +export const USER_AGENT = `n8n-nodes-cloudinary/${version}`; + +/** Standard JSON headers (Content-Type + User-Agent) shared across handlers. */ +export const jsonHeaders = (): Record => ({ + 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT, +}); + +/** HTTP Basic auth pair for the Admin API (api_key:api_secret). */ +export const basicAuth = (creds: CloudinaryCredentials): { username: string; password: string } => ({ + username: creds.apiKey, + password: creds.apiSecret, +}); + +/** + * Build the signed-upload endpoint URL for a given cloud and resource type. + */ +export const buildUploadUrl = (cloudName: string, resourceType: string): string => + `${CLOUDINARY_API_BASE}/${cloudName}/${resourceType}/upload`; + +/** + * Build the Admin API resource-update URL (used by tag/metadata updates). + */ +export const buildResourceUpdateUrl = ( + cloudName: string, + resourceType: string, + type: string, + publicId: string, +): string => `${CLOUDINARY_API_BASE}/${cloudName}/resources/${resourceType}/${type}/${publicId}`; + +/** + * Build the Admin API resource URL keyed by the immutable asset_id. Used by the + * asset_id-based endpoints (`GET`/`PUT /resources/:asset_id`). + */ +export const buildResourceByAssetIdUrl = (cloudName: string, assetId: string): string => + `${CLOUDINARY_API_BASE}/${cloudName}/resources/${assetId}`; + +/** The default shared delivery host. Accounts on a private CDN or a custom + * hostname (CNAME) deliver from elsewhere — see `buildDeliveryUrl`. */ +const SHARED_DELIVERY_HOST = 'res.cloudinary.com'; + +export interface DeliveryUrlOptions { + cloudName: string; + /** image | video | raw */ + resourceType: string; + /** upload | private | authenticated | fetch | ... */ + type: string; + publicId: string; + /** Already-joined transformation string (see `joinTransformation`). May be empty. */ + transformation?: string; + /** Target extension without the leading dot (e.g. `jpg`). */ + format?: string; + /** Asset version; emitted as a `v` path segment when present. */ + version?: string | number; + /** Account is on a private CDN: host becomes `-res.cloudinary.com`. */ + privateCdn?: boolean; + /** Custom delivery hostname (CNAME / secure_distribution). Overrides the host. */ + secureDistribution?: string; +} + +/** + * Normalize a user-supplied custom delivery hostname (CNAME / secure_distribution): + * strip any scheme and trailing slashes so we always control the `https://` prefix. + */ +const normalizeDeliveryHost = (host: string): string => + host + .trim() + .replace(/^https?:\/\//i, '') + .replace(/\/+$/, ''); + +/** + * Percent-encode a `fetch` remote source URL for use as a delivery path segment, + * mirroring the Cloudinary SDKs' "smart escape": path-safe characters + * (`A-Za-z0-9_.-/:`) stay readable while everything else — notably `?`, `#`, `&`, + * `=`, and spaces — is percent-encoded (as UTF-8, via `encodeURIComponent` on each + * unsafe run). Without this, a source like `https://host/a.jpg?sig=x#v1` would be + * split by the browser at `?`/`#`, so Cloudinary would receive only the truncated + * path and never the full remote URL. Applied to `fetch` only — the social-source + * types take opaque IDs, not URLs, so their identifiers pass through untouched. + */ +const smartEscapeFetchUrl = (source: string): string => + source.replace(/[^A-Za-z0-9_.\-/:]+/g, (run) => encodeURIComponent(run)); + +/** + * Build a Cloudinary delivery URL — the "third flow" that makes no API call. The + * host and cloud-name placement vary by account (see CLAUDE.md / Cloudinary's + * `advanced_url_delivery_options`): + * - default (shared): `https://res.cloudinary.com////...` (cloud in path) + * - private CDN: `https://-res.cloudinary.com///...` (cloud in subdomain) + * - custom hostname: `https://///...` (cloud absent) + * The cloud name is emitted in the path only on the shared host; a private CDN or + * CNAME drops it. Mirrors how the official SDKs resolve `private_cdn` / + * `secure_distribution` from explicit config rather than detection. + */ +export const buildDeliveryUrl = (opts: DeliveryUrlOptions): string => { + const { + cloudName, + resourceType, + type, + publicId, + transformation, + format, + version, + privateCdn, + secureDistribution, + } = opts; + + const cname = secureDistribution ? normalizeDeliveryHost(secureDistribution) : ''; + const host = cname || (privateCdn ? `${cloudName}-${SHARED_DELIVERY_HOST}` : SHARED_DELIVERY_HOST); + const includeCloudName = !privateCdn && !cname; + + // A `fetch` public_id is a full remote URL; escape it so query/fragment chars + // don't break the Cloudinary request path. Stored/social ids pass through. + const id = type === 'fetch' ? smartEscapeFetchUrl(publicId) : publicId; + const idWithFormat = format ? `${id}.${format}` : id; + const versionSegment = + version !== undefined && version !== '' ? `v${version}` : undefined; + + const segments = [ + includeCloudName ? cloudName : undefined, + resourceType, + type, + transformation, + versionSegment, + idWithFormat, + ].filter((s): s is string => s !== undefined && s !== ''); + + return `https://${host}/${segments.join('/')}`; +}; + +/** + * Join transformation components into the slash-separated chain Cloudinary expects. + * Commas separate qualifiers *within* a component (the caller's responsibility); + * this joins components with `/` and drops empty/whitespace-only entries so callers + * can pass conditional pieces (e.g. an optional optimize tail) without guarding. + */ +export const joinTransformation = (components: Array): string => + components + .map((c) => (c == null ? '' : c.trim())) + .filter((c) => c.length > 0) + .join('/'); + +/** + * Build the Upload API tag-action URL: `POST /:resource_type/tags`. The mutation + * itself is selected by the `command` body field (add | remove | remove_all | replace). + * Signed auth (api_key + timestamp + signature) — not Basic. + */ +export const buildTagsActionUrl = (cloudName: string, resourceType: string): string => + `${CLOUDINARY_API_BASE}/${cloudName}/${resourceType}/tags`; + +/** + * Build the Admin API bulk-delete URL: `DELETE /resources/:resource_type/:type`. + * Cloudinary's delete endpoint is always per-(resource_type, type) — there is no + * asset_id-keyed delete shape, and the public_ids are passed in the query string. + */ +export const buildResourceDeleteUrl = ( + cloudName: string, + resourceType: string, + type: string, +): string => `${CLOUDINARY_API_BASE}/${cloudName}/resources/${resourceType}/${type}`; + +/** + * Split a comma-separated string of identifiers into a trimmed, empty-filtered + * array. Used by bulk endpoints that accept "id1, id2, id3" from the user. + */ +export const splitCsvIds = (csv: string): string[] => + csv + .split(',') + .map((s) => s.trim()) + .filter((s) => s.length > 0); + +// Cloudinary's structured-metadata format documents `=`, `"`, and `|` as the +// characters that must be backslash-escaped when they appear inside a value +// (https://cloudinary.com/documentation/image_upload_api_reference#metadata). +// Comma is NOT escaped — inside a list it's the element separator. +const escapeMetadataValue = (value: string): string => value.replace(/([=|"])/g, '\\$1'); + +/** + * Convert a structured-metadata object (or JSON string) into the pipe-separated + * `key=value|key=value` string Cloudinary expects. Array values become a + * bracketed, comma-separated list of quoted strings (e.g. `color=["green","red"]`), + * matching Cloudinary's multi-value field format. Delimiter characters (`=`, `"`, + * `|`) are backslash-escaped inside every value — scalar and list element alike — + * so a value containing them can't be misparsed as another field, pair, or list + * boundary. Throws ApplicationError on invalid JSON input. + */ +export const metadataToPipeString = (input: IDataObject | string): string => { + let metadata: IDataObject; + try { + metadata = typeof input === 'object' ? input : (JSON.parse(input) as IDataObject); + } catch (error) { + throw new ApplicationError('Invalid JSON for structured metadata'); + } + return Object.keys(metadata) + .map((key) => { + const value = metadata[key]; + if (value === undefined || value === null) { + return undefined; + } + if (Array.isArray(value)) { + const items = value + .map((item) => `"${escapeMetadataValue(String(item))}"`) + .join(','); + return `${key}=[${items}]`; + } + return `${key}=${escapeMetadataValue(String(value))}`; + }) + .filter((pair): pair is string => pair !== undefined) + .join('|'); +}; /** * Generate Cloudinary signature for signed uploads @@ -17,7 +233,7 @@ export const generateCloudinarySignature = (params: IDataObject, apiSecret: stri // Append API secret const stringToSign = `${sortedParams}${apiSecret}`; - // Generate SHA1 hash using pure JavaScript implementation + // Generate the digest using the pure-JS SHA-256 implementation return sha256(stringToSign); } @@ -39,9 +255,12 @@ export const createMultipartBody = (fields: Record, fileData: Bu body += CRLF; } - // Add file field + // Add file field. Sanitize the filename: strip CR/LF (which would inject extra + // multipart headers) and replace embedded double-quotes (which would close the + // quoted filename attribute early) so a hostile or odd filename can't break framing. + const safeFileName = fileName.replace(/[\r\n]/g, '').replace(/"/g, "'"); body += `--${boundary}${CRLF}`; - body += `Content-Disposition: form-data; name="file"; filename="${fileName}"${CRLF}`; + body += `Content-Disposition: form-data; name="file"; filename="${safeFileName}"${CRLF}`; body += `Content-Type: ${mimeType}${CRLF}`; body += CRLF; @@ -52,4 +271,49 @@ export const createMultipartBody = (fields: Record, fileData: Bu const finalBody = Buffer.concat([textBuffer, fileData, endBuffer]); return { body: finalBody, boundary }; -} \ No newline at end of file +} + +/** + * Build a Cloudinary search expression, auto-injecting a `resource_type:` clause + * based on the user's selected resource types. The clause is skipped if the + * input expression already contains a `resource_type:` (or `resource_type=`) + * clause, or if all three resource types are selected (no filter needed). + */ +export const buildSearchExpression = (input: string, resourceTypes: string[]): string => { + const hasResourceTypeClause = /\bresource_type\s*[:=]/i.test(input); + if (hasResourceTypeClause || resourceTypes.length === 0 || resourceTypes.length >= 3) { + return input; + } + const clause = + resourceTypes.length === 1 + ? `resource_type:${resourceTypes[0]}` + : `resource_type:(${resourceTypes.join(' OR ')})`; + return input ? `(${input}) AND ${clause}` : clause; +}; + +export interface CloudinaryErrorInfo { + status: number | string | undefined; + retryAfter: string | undefined; + cloudinaryMessage: string | undefined; +} + +/** + * Extract status, Retry-After, and Cloudinary's error message from an n8n + * httpRequestWithAuthentication error. Only handles shapes that n8n / the + * Cloudinary server actually emit — no speculative fallbacks. + */ +export const extractCloudinaryError = (error: any): CloudinaryErrorInfo => { + const status = (error?.httpCode ?? error?.statusCode ?? error?.response?.status) as + | number + | string + | undefined; + const headers = (error?.response?.headers ?? error?.cause?.response?.headers) as + | Record + | undefined; + const retryAfter = headers?.['retry-after'] ?? headers?.['Retry-After']; + const cloudinaryMessage = + (error?.response?.body?.error?.message as string | undefined) ?? + (error?.response?.data?.error?.message as string | undefined) ?? + (headers?.['x-cld-error'] as string | undefined); + return { status, retryAfter, cloudinaryMessage }; +}; \ No newline at end of file diff --git a/nodes/Cloudinary/descriptions/admin/getTags.fields.ts b/nodes/Cloudinary/descriptions/admin/getTags.fields.ts new file mode 100644 index 0000000..95822b1 --- /dev/null +++ b/nodes/Cloudinary/descriptions/admin/getTags.fields.ts @@ -0,0 +1,62 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const getTagsFields: INodeProperties[] = [ + { + displayName: 'Resource Type', + name: 'getTagsResourceType', + type: 'options', + options: [ + { + name: 'Image', + value: 'image', + }, + { + name: 'Video', + value: 'video', + }, + { + name: 'Raw', + value: 'raw', + }, + ], + default: 'image', + description: 'The type of resource to get tags for', + required: true, + displayOptions: { + show: { + resource: ['admin'], + operation: ['getTags'], + }, + }, + }, + { + displayName: 'Prefix', + name: 'tagsPrefix', + type: 'string', + default: '', + description: 'Filter tags that start with this prefix', + displayOptions: { + show: { + resource: ['admin'], + operation: ['getTags'], + }, + }, + }, + { + displayName: 'Max Results', + name: 'tagsMaxResults', + type: 'number', + default: 100, + description: 'Maximum number of tags to return (1-500)', + typeOptions: { + minValue: 1, + maxValue: 500, + }, + displayOptions: { + show: { + resource: ['admin'], + operation: ['getTags'], + }, + }, + }, +]; diff --git a/nodes/Cloudinary/descriptions/admin/search.fields.ts b/nodes/Cloudinary/descriptions/admin/search.fields.ts new file mode 100644 index 0000000..259e2af --- /dev/null +++ b/nodes/Cloudinary/descriptions/admin/search.fields.ts @@ -0,0 +1,121 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const searchFields: INodeProperties[] = [ + { + displayName: 'Expression', + name: 'searchExpression', + type: 'string', + default: '', + description: + 'Cloudinary search expression. Examples: tags=cat AND uploaded_at>1d, folder:products/*, tags="back to school" (quote values with spaces). Leave empty to match all assets. Full expression syntax →', + displayOptions: { + show: { + resource: ['asset'], + operation: ['search'], + }, + }, + }, + { + displayName: 'Resource Types', + name: 'searchResourceTypes', + type: 'multiOptions', + options: [ + { name: 'Image', value: 'image' }, + { name: 'Raw', value: 'raw' }, + { name: 'Video', value: 'video' }, + ], + default: ['image'], + description: + 'Resource types to search. Cloudinary defaults to image-only — select Video and/or Raw to include them. Ignored if your Expression already contains resource_type:.', + displayOptions: { + show: { + resource: ['asset'], + operation: ['search'], + }, + }, + }, + { + displayName: 'Return All', + name: 'searchReturnAll', + type: 'boolean', + default: false, + description: 'Whether to return all matching assets by automatically paginating through results', + displayOptions: { + show: { + resource: ['asset'], + operation: ['search'], + }, + }, + }, + { + displayName: 'Max Results', + name: 'searchMaxResults', + type: 'number', + default: 50, + description: 'Maximum number of assets to return (1-500). Ignored when Return All is enabled.', + typeOptions: { + minValue: 1, + maxValue: 500, + }, + displayOptions: { + show: { + resource: ['asset'], + operation: ['search'], + searchReturnAll: [false], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'searchAdditionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['asset'], + operation: ['search'], + }, + }, + default: {}, + options: [ + { + displayName: 'Next Cursor', + name: 'next_cursor', + type: 'string', + default: '', + description: 'Pagination cursor returned by a previous search response', + }, + { + displayName: 'Sort By Field', + name: 'sortField', + type: 'string', + default: 'created_at', + description: + 'Field to sort results by (e.g. created_at, public_id, uploaded_at)', + }, + { + displayName: 'Sort Direction', + name: 'sortDirection', + type: 'options', + options: [ + { name: 'Ascending', value: 'asc' }, + { name: 'Descending', value: 'desc' }, + ], + default: 'desc', + description: 'Sort direction for the Sort By Field', + }, + { + displayName: 'With Field', + name: 'with_field', + type: 'multiOptions', + options: [ + { name: 'Context', value: 'context' }, + { name: 'Tags', value: 'tags' }, + ], + default: [], + description: + 'Extra attributes to include for each returned asset. By default the search response omits these to keep payloads small. Enable Tags to get each asset\'s tag list, or Context to get its contextual key-value metadata (e.g. alt text, captions) — useful when you want to filter or branch on those values downstream without a second lookup.', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/descriptions/asset.fields.ts b/nodes/Cloudinary/descriptions/asset.fields.ts new file mode 100644 index 0000000..2301c59 --- /dev/null +++ b/nodes/Cloudinary/descriptions/asset.fields.ts @@ -0,0 +1,361 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const assetFields: INodeProperties[] = [ + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + default: '', + description: 'The immutable asset_id of the asset', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['getAsset', 'updateMetadata'], + }, + }, + }, + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + default: '', + description: 'The immutable asset_id of the asset', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + tagMode: ['set'], + }, + }, + }, + { + displayName: 'Mode', + name: 'tagMode', + type: 'options', + options: [ + { + name: 'Set (Replace All Existing Tags)', + value: 'set', + description: "Replace the asset's tag list with the values you provide", + }, + { + name: 'Append (Add to Existing Tags)', + value: 'append', + description: 'Add the provided tags to the existing list — existing tags are preserved', + }, + ], + default: 'set', + description: 'How to apply the provided tags. Append uses the public_id-keyed Upload API.', + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + }, + }, + }, + { + displayName: 'Public ID(s)', + name: 'tagAppendPublicIds', + type: 'string', + default: '', + description: + 'Asset(s) to tag, by public ID. Paste a single public_id, several separated by commas, or wire up an expression — both single values and arrays from expressions are accepted. Cloudinary\'s tag-append endpoint is keyed on public_id; asset_id is not accepted here.', + hint: 'A single public_id is fine — no commas needed. Expressions returning an array also work.', + placeholder: 'docs/strawberry or docs/strawberry, docs/owl', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + tagMode: ['append'], + }, + }, + }, + { + displayName: 'Resource Type', + name: 'tagAppendResourceType', + type: 'options', + options: [ + { name: 'Image', value: 'image' }, + { name: 'Video', value: 'video' }, + { name: 'Raw', value: 'raw' }, + ], + default: 'image', + description: 'The type of asset to tag', + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + tagMode: ['append'], + }, + }, + }, + { + displayName: 'Type', + name: 'tagAppendType', + type: 'options', + options: [ + { name: 'Upload', value: 'upload' }, + { name: 'Private', value: 'private' }, + { name: 'Authenticated', value: 'authenticated' }, + { name: 'Fetch', value: 'fetch' }, + ], + default: 'upload', + description: + 'The "type" property of the asset (as returned by Cloudinary on the asset object). Must match — public_ids are scoped to (resource_type, type), so an asset stored as "authenticated" is invisible to the default "upload" lookup.', + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + tagMode: ['append'], + }, + }, + }, + { + displayName: 'Public ID(s)', + name: 'publicIds', + type: 'string', + default: '', + description: + 'Asset(s) to delete, by public ID. Paste a single public_id, several separated by commas, or wire up an expression — both single values and arrays from expressions are accepted.', + hint: 'A single public_id is fine — no commas needed. Expressions returning an array also work.', + placeholder: 'docs/strawberry or docs/strawberry, docs/owl', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['deleteAssets'], + }, + }, + }, + { + displayName: 'Resource Type', + name: 'resourceType', + type: 'options', + options: [ + { name: 'Image', value: 'image' }, + { name: 'Video', value: 'video' }, + { name: 'Raw', value: 'raw' }, + ], + default: 'image', + description: 'The type of asset to delete', + displayOptions: { + show: { + resource: ['asset'], + operation: ['deleteAssets'], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { name: 'Upload', value: 'upload' }, + { name: 'Private', value: 'private' }, + { name: 'Authenticated', value: 'authenticated' }, + { name: 'Fetch', value: 'fetch' }, + ], + default: 'upload', + description: 'The storage type of the asset', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['deleteAssets'], + }, + }, + }, + { + displayName: 'Delete Options', + name: 'deleteOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['asset'], + operation: ['deleteAssets'], + }, + }, + default: {}, + options: [ + { + displayName: 'Invalidate CDN', + name: 'invalidate', + type: 'boolean', + default: false, + description: 'Whether to invalidate CDN cache copies of the deleted assets', + }, + { + displayName: 'Keep Original', + name: 'keep_original', + type: 'boolean', + default: false, + description: + 'Whether to keep the original asset and only delete its derived (transformed) versions', + }, + { + displayName: 'Next Cursor', + name: 'next_cursor', + type: 'string', + default: '', + description: 'Pagination cursor returned by a previous delete response for large batches', + }, + { + displayName: 'Transformations', + name: 'transformations', + type: 'string', + default: '', + description: + 'Comma-separated list of transformation strings; only the listed derived assets are deleted', + }, + ], + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'A comma-separated list of tag names to assign to the asset', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + }, + }, + }, + { + displayName: 'Structured Metadata', + name: 'structuredMetadata', + type: 'json', + default: '{}', + description: + 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', + required: true, + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateMetadata'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateMetadata'], + }, + }, + default: {}, + options: [ + { + displayName: 'Invalidate CDN', + name: 'invalidate', + type: 'boolean', + default: false, + description: 'Whether to invalidate CDN cache copies of the asset', + }, + ], + }, + { + displayName: 'Update Fields', + name: 'updateOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['asset'], + operation: ['updateTags'], + tagMode: ['set'], + }, + }, + default: {}, + options: [ + { + displayName: 'Invalidate CDN', + name: 'invalidate', + type: 'boolean', + default: false, + description: 'Whether to invalidate CDN cache copies of the asset', + }, + ], + }, + { + displayName: 'Options', + name: 'getOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['asset'], + operation: ['getAsset'], + }, + }, + default: {}, + options: [ + { + displayName: 'Accessibility Analysis', + name: 'accessibility_analysis', + type: 'boolean', + default: false, + description: 'Whether to include accessibility analysis data in the response', + }, + { + displayName: 'Colors', + name: 'colors', + type: 'boolean', + default: false, + description: 'Whether to include the predominant colors and color histogram of the asset', + }, + { + displayName: 'Coordinates', + name: 'coordinates', + type: 'boolean', + default: false, + description: 'Whether to include custom and detected coordinates (e.g. faces, custom crop regions)', + }, + { + displayName: 'Derived Next Cursor', + name: 'derived_next_cursor', + type: 'string', + default: '', + description: 'Pagination cursor for retrieving additional derived assets beyond the first page', + }, + { + displayName: 'Faces', + name: 'faces', + type: 'boolean', + default: false, + description: 'Whether to include coordinates of detected faces', + }, + { + displayName: 'Image Metadata', + name: 'image_metadata', + type: 'boolean', + default: false, + description: 'Whether to include IPTC, XMP, and EXIF metadata extracted from the asset', + }, + { + displayName: 'Pages', + name: 'pages', + type: 'boolean', + default: false, + description: 'Whether to include the number of pages in multi-page assets (e.g. PDF, animated GIF, TIFF)', + }, + { + displayName: 'Perceptual Hash (pHash)', + name: 'phash', + type: 'boolean', + default: false, + description: 'Whether to include the perceptual hash (pHash) of the uploaded asset', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/descriptions/index.ts b/nodes/Cloudinary/descriptions/index.ts new file mode 100644 index 0000000..f3a7b4b --- /dev/null +++ b/nodes/Cloudinary/descriptions/index.ts @@ -0,0 +1,20 @@ +import { INodeProperties } from 'n8n-workflow'; +import { resourceProperties } from './resource'; +import { uploadFields } from './upload.fields'; +import { transformFields } from './transform.fields'; +import { widgetFields } from './widget.fields'; +import { updateAssetFields } from './updateAsset.fields'; +import { assetFields } from './asset.fields'; +import { searchFields } from './admin/search.fields'; +import { getTagsFields } from './admin/getTags.fields'; + +export const cloudinaryProperties: INodeProperties[] = [ + ...resourceProperties, + ...uploadFields, + ...transformFields, + ...widgetFields, + ...assetFields, + ...updateAssetFields, + ...searchFields, + ...getTagsFields, +]; diff --git a/nodes/Cloudinary/descriptions/resource.ts b/nodes/Cloudinary/descriptions/resource.ts new file mode 100644 index 0000000..0e92486 --- /dev/null +++ b/nodes/Cloudinary/descriptions/resource.ts @@ -0,0 +1,264 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const resourceProperties: INodeProperties[] = [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + // Order is curated for usefulness, not alphabetized: the primary Upload and + // Transform flows lead, and the deprecated legacy resource sinks to the bottom. + // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items + options: [ + { + name: 'Upload', + value: 'upload', + description: 'Upload new assets from a URL or binary file data', + }, + { + name: 'Transform', + value: 'transform', + description: 'Build delivery and transformation URLs for images and videos (no upload, no API call)', + }, + { + name: 'Asset', + value: 'asset', + description: 'Work with existing assets by asset ID: get, search, delete, update tags/metadata', + }, + { + name: 'Widget', + value: 'widget', + description: 'Generate Cloudinary widgets and embeds, such as the Video Player (no upload, no API call)', + }, + { + name: 'Library', + value: 'admin', + description: 'Account-level lookups: list tags and structured-metadata field definitions', + }, + { + name: 'Asset (Legacy, by Public ID)', + value: 'updateAsset', + description: 'Deprecated — prefer the Asset resource. Public-ID-based tag and metadata updates.', + }, + ], + default: 'upload', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['upload'], + }, + }, + options: [ + { + name: 'Upload File', + value: 'uploadFile', + description: 'Upload an asset from file data', + action: 'Upload an asset from file data', + }, + { + name: 'Upload From URL', + value: 'uploadUrl', + description: 'Upload an asset from URL', + action: 'Upload an asset from URL', + }, + ], + default: 'uploadUrl', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['transform'], + }, + }, + // Each name/action is prefixed with a category ("Compose:"/"Image:"/"Video:") so the + // alphabetical lint rule clusters them into groups in both the operation dropdown + // and the Add-action panel. "Compose" sorts before "Image"/"Video", so the flagship + // Combine Transformations leads the list. name and action are kept identical so both + // surfaces read the same; that requires disabling the sentence-case action rule, + // which would otherwise strip the colon and lower-case the label. + /* eslint-disable n8n-nodes-base/node-param-operation-option-action-miscased */ + options: [ + { + name: 'Compose: Combine Transformations', + value: 'combineTransformations', + description: 'Build a delivery URL that chains several transformation steps in order. Outputs secure_url and a reusable transformation string.', + action: 'Compose: Combine Transformations', + }, + { + name: 'Compose: Custom Transformation String', + value: 'customTransformation', + description: 'Build a delivery URL from a raw Cloudinary transformation string. Outputs secure_url and a reusable transformation string.', + action: 'Compose: Custom Transformation String', + }, + { + name: 'Image: Convert Format', + value: 'convertImage', + description: 'Build a delivery URL that converts an image to another format. Outputs secure_url and a reusable transformation string.', + action: 'Image: Convert Format', + }, + { + name: 'Image: Crop', + value: 'cropImage', + description: 'Build a delivery URL that crops an image to fixed dimensions or an aspect ratio. Outputs secure_url and a reusable transformation string.', + action: 'Image: Crop', + }, + { + name: 'Image: Optimize', + value: 'optimizeImage', + description: 'Build a delivery URL that auto-optimizes an image (format + quality). Outputs secure_url and a reusable transformation string.', + action: 'Image: Optimize', + }, + { + name: 'Image: Resize', + value: 'resizeImage', + description: 'Build a delivery URL that resizes an image to a width and/or height. Outputs secure_url and a reusable transformation string.', + action: 'Image: Resize', + }, + { + name: 'Video: Optimize', + value: 'optimizeVideo', + description: 'Build a delivery URL that auto-optimizes a video (format/codec + quality). Outputs secure_url and a reusable transformation string.', + action: 'Video: Optimize', + }, + { + name: 'Video: Thumbnail', + value: 'videoThumbnail', + description: 'Build a delivery URL for a still image frame from a video. Outputs secure_url and a reusable transformation string.', + action: 'Video: Thumbnail', + }, + { + name: 'Video: Trim', + value: 'trimVideo', + description: 'Build a delivery URL that trims a video to a start, end, and/or duration. Outputs secure_url and a reusable transformation string.', + action: 'Video: Trim', + }, + ], + /* eslint-enable n8n-nodes-base/node-param-operation-option-action-miscased */ + default: 'optimizeImage', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['widget'], + }, + }, + options: [ + { + name: 'Video Player', + value: 'videoPlayer', + description: 'Generate embed code and config for the Cloudinary Video Player. Outputs embed_url and a player_config JSON string.', + action: 'Generate a video player', + }, + ], + default: 'videoPlayer', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['asset'], + }, + }, + options: [ + { + name: 'Delete Assets', + value: 'deleteAssets', + description: 'Delete one or more assets by public ID', + action: 'Delete assets', + }, + { + name: 'Get Asset', + value: 'getAsset', + description: 'Get details for a single asset by asset ID', + action: 'Get an asset', + }, + { + name: 'Search Assets', + value: 'search', + description: 'Search for assets using a Cloudinary search expression', + action: 'Search assets', + }, + { + name: 'Update Asset Structured Metadata', + value: 'updateMetadata', + description: 'Update structured metadata for an asset by asset ID', + action: 'Update asset structured metadata', + }, + { + name: 'Update Asset Tags', + value: 'updateTags', + description: 'Update tags for an asset by asset ID', + action: 'Update asset tags', + }, + ], + default: 'getAsset', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['updateAsset'], + }, + }, + options: [ + { + name: 'Update Asset Structured Metadata', + value: 'updateMetadata', + description: 'Deprecated — use the Asset resource instead. Update structured metadata for an existing asset by public ID.', + action: 'Update asset structured metadata', + }, + { + name: 'Update Asset Tags', + value: 'updateTags', + description: 'Deprecated — use the Asset resource instead. Update tags for an existing asset by public ID.', + action: 'Update asset tags', + }, + ], + default: 'updateTags', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['admin'], + }, + }, + options: [ + { + name: 'Get Metadata Fields', + value: 'getMetadataFields', + description: 'Get all metadata fields definitions', + action: 'Get metadata fields definitions', + }, + { + name: 'Get Tags', + value: 'getTags', + description: 'Get all tags for a specific resource type', + action: 'Get tags for a resource type', + }, + ], + default: 'getTags', + }, +]; diff --git a/nodes/Cloudinary/descriptions/transform.fields.ts b/nodes/Cloudinary/descriptions/transform.fields.ts new file mode 100644 index 0000000..a89a2ba --- /dev/null +++ b/nodes/Cloudinary/descriptions/transform.fields.ts @@ -0,0 +1,701 @@ +import { INodeProperties } from 'n8n-workflow'; + +// All Transform operations build a Cloudinary *delivery URL* and make no API call +// (the "third flow" — see CLAUDE.md). Fields are gated by resource:'transform' plus +// the operation. Field names mirror the Cloudinary API's response property names where +// one exists (`type`, matching the API's `type` and the existing asset/updateAsset +// fields). Transform-specific knobs that have no API counterpart use `transform`-/ +// op-prefixed names to stay unambiguous. + +// Every op that references a stored asset by public_id — the Transform ops plus the +// Widgets `videoPlayer` op (Widgets resource), which shares the Public ID + Type +// identity fields below even though its own controls live in widget.fields.ts. +const ALL_TRANSFORM_OPS = [ + 'optimizeImage', + 'resizeImage', + 'cropImage', + 'convertImage', + 'optimizeVideo', + 'trimVideo', + 'videoThumbnail', + 'customTransformation', + 'combineTransformations', + 'videoPlayer', +]; + +// Delivery-URL ops only — excludes videoPlayer, which builds an embed URL (not a +// delivery URL) and has no use for the version/CDN-cache-bust Additional Option. +const DELIVERY_URL_OPS = ALL_TRANSFORM_OPS.filter((op) => op !== 'videoPlayer'); + +const QUALITY_OPTIONS = [ + { name: 'Auto (Recommended)', value: 'auto', description: 'Let Cloudinary choose the best quality/size trade-off' }, + { name: 'Auto - Best', value: 'best', description: 'Higher quality, larger files' }, + { name: 'Auto - Good', value: 'good', description: 'Balanced quality' }, + { name: 'Auto - Eco', value: 'eco', description: 'Smaller files, slightly lower quality' }, + { name: 'Auto - Low', value: 'low', description: 'Smallest files, lowest quality' }, +]; + +export const transformFields: INodeProperties[] = [ + // ── Shared identity (all transform operations) ─────────────────────────── + { + displayName: 'Public ID', + name: 'transformPublicId', + type: 'string', + default: '', + required: true, + placeholder: 'folder/sample', + description: + 'Public ID of the asset, including any folder path and without the file extension', + displayOptions: { + show: { + resource: ['transform', 'widget'], + operation: ALL_TRANSFORM_OPS, + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { name: 'Animoto', value: 'animoto' }, + { name: 'Authenticated', value: 'authenticated' }, + { name: 'Dailymotion', value: 'dailymotion' }, + { name: 'Facebook', value: 'facebook' }, + { name: 'Fetch (Remote URL)', value: 'fetch' }, + { name: 'Gravatar', value: 'gravatar' }, + { name: 'Hulu', value: 'hulu' }, + { name: 'Private', value: 'private' }, + { name: 'Twitter (by ID)', value: 'twitter' }, + { name: 'Twitter (by Name)', value: 'twitter_name' }, + { name: 'Upload (Public)', value: 'upload' }, + { name: 'Vimeo', value: 'vimeo' }, + { name: 'WorldStarHipHop', value: 'worldstarhiphop' }, + { name: 'YouTube', value: 'youtube' }, + ], + default: 'upload', + description: 'Cloudinary delivery type — in almost all cases use "Upload" (public assets). Becomes the URL path segment (e.g. image/upload/sample, video/fetch/<URL>). IMPORTANT: "Private" and "Authenticated" assets — and, depending on account settings, social profile sources (Facebook, Twitter, Gravatar) — require a signed URL, which this node does not generate yet, so those delivery URLs will fail to load. Leave as "Upload" unless you specifically need another type.', + displayOptions: { + show: { + resource: ['transform', 'widget'], + operation: ALL_TRANSFORM_OPS, + }, + }, + }, + + // ── Optimize Image ─────────────────────────────────────────────────────── + { + displayName: 'Quality', + name: 'imageQuality', + type: 'options', + options: QUALITY_OPTIONS, + default: 'auto', + description: 'Automatic quality level. Format is always auto-selected (f_auto).', + displayOptions: { + show: { resource: ['transform'], operation: ['optimizeImage'] }, + }, + }, + + // ── Resize Image ───────────────────────────────────────────────────────── + { + displayName: 'Width', + name: 'resizeWidth', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Target width in pixels. Leave 0 to size by height only.', + displayOptions: { + show: { resource: ['transform'], operation: ['resizeImage'] }, + }, + }, + { + displayName: 'Height', + name: 'resizeHeight', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Target height in pixels. Leave 0 to size by width only.', + displayOptions: { + show: { resource: ['transform'], operation: ['resizeImage'] }, + }, + }, + { + displayName: 'Fit', + name: 'resizeFit', + type: 'options', + options: [ + { name: 'Limit (Never Upscale)', value: 'limit', description: 'Resize down to fit within the dimensions; never enlarges' }, + { name: 'Fit (Fit Within)', value: 'fit', description: 'Fit within the dimensions, may enlarge, keeps full image' }, + { name: 'Scale (Exact)', value: 'scale', description: 'Force exact dimensions; may distort if both are set' }, + ], + default: 'limit', + description: 'How the image is fitted to the requested dimensions. Note: Resize does not auto-optimize — chain "Image: Optimize" after it, or use "Compose: Combine Transformations", to add f_auto/q_auto.', + displayOptions: { + show: { resource: ['transform'], operation: ['resizeImage'] }, + }, + }, + + // ── Crop Image ─────────────────────────────────────────────────────────── + { + displayName: 'Crop By', + name: 'cropBy', + type: 'options', + options: [ + { name: 'Dimensions', value: 'dimensions', description: 'Crop to an exact width and height' }, + { name: 'Aspect Ratio', value: 'aspectRatio', description: 'Crop to an aspect ratio (e.g. 16:9)' }, + ], + default: 'dimensions', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'] }, + }, + }, + { + displayName: 'Width', + name: 'cropWidth', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Target width in pixels', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'], cropBy: ['dimensions'] }, + }, + }, + { + displayName: 'Height', + name: 'cropHeight', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Target height in pixels', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'], cropBy: ['dimensions'] }, + }, + }, + { + displayName: 'Aspect Ratio', + name: 'cropAspectRatio', + type: 'string', + default: '', + placeholder: '16:9', + description: 'Aspect ratio as width:height (e.g. 16:9) or a decimal (e.g. 1.5).', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'], cropBy: ['aspectRatio'] }, + }, + }, + { + displayName: 'Width', + name: 'cropAspectWidth', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Optional target width in pixels; height is derived from the aspect ratio', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'], cropBy: ['aspectRatio'] }, + }, + }, + { + displayName: 'Focus', + name: 'cropFocus', + type: 'options', + options: [ + { name: 'Auto (Smart)', value: 'auto', description: 'Automatically detect the most important region' }, + { name: 'Face', value: 'face', description: 'Center the crop on a detected face' }, + { name: 'Center', value: 'center', description: 'Center the crop on the middle of the image' }, + ], + default: 'auto', + description: 'Which part of the image to keep in focus when cropping. Note: Crop does not auto-optimize — chain "Image: Optimize" after it, or use "Compose: Combine Transformations", to add f_auto/q_auto.', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'] }, + }, + }, + { + displayName: 'Generative Fill', + name: 'cropGenerativeFill', + type: 'boolean', + default: false, + description: + 'Whether to extend (pad) the image with AI-generated content to reach the target shape instead of cropping. This is a generative AI effect that costs extra transformation credits, applies to non-transparent images only, and may render asynchronously on first request.', + displayOptions: { + show: { resource: ['transform'], operation: ['cropImage'] }, + }, + }, + { + displayName: 'Generative Fill Prompt', + name: 'cropGenerativeFillPrompt', + type: 'string', + default: '', + placeholder: 'a sandy beach', + description: 'Optional text prompt guiding the generated fill content', + displayOptions: { + show: { + resource: ['transform'], + operation: ['cropImage'], + cropGenerativeFill: [true], + }, + }, + }, + + // ── Convert Image ──────────────────────────────────────────────────────── + { + displayName: 'Target Format', + name: 'convertFormat', + type: 'options', + options: [ + { name: 'AVIF', value: 'avif' }, + { name: 'GIF', value: 'gif' }, + { name: 'JPG', value: 'jpg' }, + { name: 'PNG', value: 'png' }, + { name: 'WebP', value: 'webp' }, + ], + default: 'webp', + description: 'Format to convert the image to', + displayOptions: { + show: { resource: ['transform'], operation: ['convertImage'] }, + }, + }, + + // ── Optimize Video ─────────────────────────────────────────────────────── + { + displayName: 'Quality', + name: 'videoQuality', + type: 'options', + options: QUALITY_OPTIONS, + default: 'auto', + description: + 'Automatic quality level. Format and codec are auto-selected for the requesting browser (f_auto:video).', + displayOptions: { + show: { resource: ['transform'], operation: ['optimizeVideo'] }, + }, + }, + + // ── Trim Video ─────────────────────────────────────────────────────────── + { + displayName: 'Start Offset', + name: 'trimStart', + type: 'string', + default: '', + placeholder: '2.5', + description: 'Start time in seconds (e.g. 2.5). Maps to so_.', + displayOptions: { + show: { resource: ['transform'], operation: ['trimVideo'] }, + }, + }, + { + displayName: 'End Offset', + name: 'trimEnd', + type: 'string', + default: '', + placeholder: '10', + description: 'End time in seconds (e.g. 10). Maps to eo_.', + displayOptions: { + show: { resource: ['transform'], operation: ['trimVideo'] }, + }, + }, + { + displayName: 'Duration', + name: 'trimDuration', + type: 'string', + default: '', + placeholder: '5', + description: 'Clip duration in seconds (e.g. 5). Maps to du_. Any combination of start/end/duration is allowed.', + displayOptions: { + show: { resource: ['transform'], operation: ['trimVideo'] }, + }, + }, + + // ── Video Thumbnail ────────────────────────────────────────────────────── + { + displayName: 'Frame', + name: 'thumbnailFrameMode', + type: 'options', + options: [ + { name: 'Auto', value: 'auto', description: 'Let Cloudinary pick a representative frame (so_auto)' }, + { name: 'Specific Time', value: 'time', description: 'Grab the frame at a specific timestamp' }, + ], + default: 'auto', + displayOptions: { + show: { resource: ['transform'], operation: ['videoThumbnail'] }, + }, + }, + { + displayName: 'Timestamp', + name: 'thumbnailTimestamp', + type: 'string', + default: '', + placeholder: '2.5', + description: 'Timestamp in seconds to grab the frame from (e.g. 2.5)', + displayOptions: { + show: { + resource: ['transform'], + operation: ['videoThumbnail'], + thumbnailFrameMode: ['time'], + }, + }, + }, + { + displayName: 'Image Format', + name: 'thumbnailFormat', + type: 'options', + options: [ + { name: 'JPG', value: 'jpg' }, + { name: 'PNG', value: 'png' }, + { name: 'WebP', value: 'webp' }, + { name: 'GIF', value: 'gif' }, + ], + default: 'jpg', + description: 'Format of the generated thumbnail image', + displayOptions: { + show: { resource: ['transform'], operation: ['videoThumbnail'] }, + }, + }, + { + displayName: 'Base Transformation', + name: 'thumbnailBaseTransformation', + type: 'string', + default: '', + placeholder: 'c_fill,w_800,h_600', + // `$json` is the n8n expression variable — correctly lowercase. The + // miscased-json rule is a false positive here; its autofix would rewrite it + // to `$JSON`, which is undefined in n8n and breaks the example. Keep lowercase. + // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-json + description: 'Cloudinary transformation string prepended before the frame selector. Use {{ $json.transformation }} to chain from a previous step. Accepts a raw transformation string (e.g. c_fill,w_800,h_600).', + displayOptions: { + show: { resource: ['transform'], operation: ['videoThumbnail'] }, + }, + }, + + // ── Custom Transformation ──────────────────────────────────────────────── + { + displayName: 'Resource Type', + name: 'customResourceType', + type: 'options', + options: [ + { name: 'Image', value: 'image' }, + { name: 'Video', value: 'video' }, + ], + default: 'image', + description: 'The type of asset the transformation targets', + displayOptions: { + show: { resource: ['transform'], operation: ['customTransformation'] }, + }, + }, + { + displayName: 'Transformation', + name: 'customTransformationString', + type: 'string', + default: '', + required: true, + placeholder: 'c_fill,g_auto,w_400,h_300/f_auto/q_auto', + description: + 'Raw Cloudinary transformation string, used verbatim. Components are separated by "/" and qualifiers within a component by ",".', + displayOptions: { + show: { resource: ['transform'], operation: ['customTransformation'] }, + }, + }, + { + displayName: 'Format', + name: 'customFormat', + type: 'string', + default: '', + placeholder: 'jpg', + description: 'Optional delivery format (file extension), without the leading dot', + displayOptions: { + show: { resource: ['transform'], operation: ['customTransformation'] }, + }, + }, + + // ── Combine Transformations (multi-step) ───────────────────────────────── + { + displayName: 'Resource Type', + name: 'multiStepResourceType', + type: 'options', + options: [ + { name: 'Image', value: 'image' }, + { name: 'Video', value: 'video' }, + ], + default: 'image', + description: 'The type of asset the steps target. Determines the URL path and whether Optimize emits f_auto or f_auto:video.', + displayOptions: { + show: { resource: ['transform'], operation: ['combineTransformations'] }, + }, + }, + { + displayName: 'Steps', + name: 'transformSteps', + type: 'fixedCollection', + placeholder: 'Add Step', + typeOptions: { multipleValues: true, sortable: true }, + default: {}, + description: + 'Transformation steps applied in order. Each step becomes one chained Cloudinary component (drag to reorder).', + displayOptions: { + show: { resource: ['transform'], operation: ['combineTransformations'] }, + }, + options: [ + { + displayName: 'Step', + name: 'step', + values: [ + { + displayName: 'Action', + name: 'stepType', + type: 'options', + options: [ + { + name: 'Convert Format', + value: 'convert', + description: 'Set the delivery format (f_<fmt>\t+\textension)', + }, + { + name: 'Crop', + value: 'crop', + description: 'Crop to dimensions or an aspect ratio (c_fill)', + }, + { + name: 'Optimize', + value: 'optimize', + description: 'Auto format + quality (f_auto[:video ],q_auto)', + }, + { + name: 'Raw Component', + value: 'raw', + description: 'A verbatim transformation component', + }, + { + name: 'Resize', + value: 'resize', + description: 'Resize to a width and/or height (c_<fit>)', + }, + { + name: 'Trim (Video)', + value: 'trim', + description: 'Trim by start/end/duration (so_/eo_/du_)', + }, + ], + default: 'optimize', + description: 'The transformation this step applies', + }, + { + displayName: 'Aspect Ratio', + name: 'aspectRatio', + type: 'string', + default: '', + placeholder: '9:16', + description: 'Aspect ratio as width:height (e.g. 9:16) or a decimal (e.g. 1.5).', + displayOptions: { + show: { stepType: ['crop'], cropMode: ['aspectRatio'] }, + }, + }, + { + displayName: 'Component', + name: 'raw', + type: 'string', + default: '', + placeholder: 'e_grayscale', + description: 'A raw Cloudinary transformation component, used verbatim (qualifiers separated by commas)', + displayOptions: { + show: { stepType: ['raw'] }, + }, + }, + { + displayName: 'Crop By', + name: 'cropMode', + type: 'options', + options: [ + { + name: 'Dimensions', + value: 'dimensions', + }, + { + name: 'Aspect Ratio', + value: 'aspectRatio', + }, + ], + default: 'dimensions', + displayOptions: { + show: { stepType: ['crop'] }, + }, + }, + { + displayName: 'Duration', + name: 'duration', + type: 'string', + default: '', + placeholder: '10', + description: 'Clip duration in seconds (du_)', + displayOptions: { + show: { stepType: ['trim'] }, + }, + }, + { + displayName: 'End Offset', + name: 'end', + type: 'string', + default: '', + placeholder: '15', + description: 'End time in seconds (eo_)', + displayOptions: { + show: { stepType: ['trim'] }, + }, + }, + { + displayName: 'Fit', + name: 'fit', + type: 'options', + options: [ + { + name: 'Limit (Never Upscale)', + value: 'limit', + }, + { + name: 'Fit (Fit Within)', + value: 'fit', + }, + { + name: 'Scale (Exact)', + value: 'scale', + }, + ], + default: 'limit', + description: 'How the asset is fitted to the requested dimensions', + displayOptions: { + show: { stepType: ['resize'] }, + }, + }, + { + displayName: 'Focus', + name: 'focus', + type: 'options', + options: [ + { + name: 'Auto (Smart)', + value: 'auto', + }, + { + name: 'Face', + value: 'face', + }, + { + name: 'Center', + value: 'center', + }, + ], + default: 'auto', + description: 'Which region to keep when cropping (g_)', + displayOptions: { + show: { stepType: ['crop'] }, + }, + }, + { + displayName: 'Height', + name: 'height', + type: 'number', + default: 0, + description: 'Target height in pixels. Leave 0 to size by width only.', + displayOptions: { + show: { stepType: ['resize'] }, + }, + }, + { + displayName: 'Height', + name: 'cropHeight', + type: 'number', + default: 0, + description: 'Target height in pixels', + displayOptions: { + show: { stepType: ['crop'], cropMode: ['dimensions'] }, + }, + }, + { + displayName: 'Quality', + name: 'quality', + type: 'options', + options: QUALITY_OPTIONS, + default: 'auto', + description: 'Automatic quality level (q_auto[:level])', + displayOptions: { + show: { stepType: ['optimize'] }, + }, + }, + { + displayName: 'Start Offset', + name: 'start', + type: 'string', + default: '', + placeholder: '0', + description: 'Start time in seconds (so_)', + displayOptions: { + show: { stepType: ['trim'] }, + }, + }, + { + displayName: 'Target Format', + name: 'format', + type: 'string', + default: '', + placeholder: 'webp', + description: 'Delivery format (file extension), without the leading dot', + displayOptions: { + show: { stepType: ['convert'] }, + }, + }, + { + displayName: 'Width', + name: 'width', + type: 'number', + default: 0, + description: 'Target width in pixels. Leave 0 to size by height only.', + displayOptions: { + show: { stepType: ['resize'] }, + }, + }, + { + displayName: 'Width', + name: 'cropWidth', + type: 'number', + default: 0, + description: 'Target width in pixels', + displayOptions: { + show: { stepType: ['crop'], cropMode: ['dimensions'] }, + }, + }, + { + displayName: 'Width', + name: 'cropAspectWidth', + type: 'number', + default: 0, + description: 'Optional target width in pixels; height is derived from the aspect ratio', + displayOptions: { + show: { stepType: ['crop'], cropMode: ['aspectRatio'] }, + }, + }, + ], + }, + ], + }, + + // ── Shared additional options (delivery-URL operations only) ──────────────── + { + displayName: 'Additional Options', + name: 'transformAdditionalOptions', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['transform'], + operation: DELIVERY_URL_OPS, + }, + }, + options: [ + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + placeholder: '1234567890', + description: 'Asset version, emitted as a v<version> segment to bust CDN caches', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/descriptions/updateAsset.fields.ts b/nodes/Cloudinary/descriptions/updateAsset.fields.ts new file mode 100644 index 0000000..363d5c7 --- /dev/null +++ b/nodes/Cloudinary/descriptions/updateAsset.fields.ts @@ -0,0 +1,263 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const updateAssetFields: INodeProperties[] = [ + { + displayName: 'Public ID', + name: 'publicId', + type: 'string', + default: '', + description: 'The public ID of the asset to update', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateMetadata'], + }, + }, + }, + { + displayName: 'Public ID', + name: 'publicId', + type: 'string', + default: '', + description: 'The public ID of the asset to update', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + tagMode: ['set'], + }, + }, + }, + { + displayName: 'Resource Type', + name: 'resourceType', + type: 'options', + options: [ + { + name: 'Image', + value: 'image', + }, + { + name: 'Video', + value: 'video', + }, + { + name: 'Raw', + value: 'raw', + }, + ], + default: 'image', + description: 'The type of asset to update', + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags', 'updateMetadata'], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Upload', + value: 'upload', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Authenticated', + value: 'authenticated', + }, + { + name: 'Fetch', + value: 'fetch', + }, + ], + default: 'upload', + description: 'The storage type of the asset', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateMetadata'], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Upload', + value: 'upload', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Authenticated', + value: 'authenticated', + }, + { + name: 'Fetch', + value: 'fetch', + }, + ], + default: 'upload', + description: 'The storage type of the asset', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + tagMode: ['set'], + }, + }, + }, + { + displayName: 'Mode', + name: 'tagMode', + type: 'options', + options: [ + { + name: 'Set (Replace All Existing Tags)', + value: 'set', + description: "Replace the asset's tag list with the values you provide", + }, + { + name: 'Append (Add to Existing Tags)', + value: 'append', + description: 'Add the provided tags to the existing list — existing tags are preserved', + }, + ], + default: 'set', + description: 'How to apply the provided tags. Append uses Cloudinary\'s tag-action endpoint.', + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + }, + }, + }, + { + displayName: 'Type', + name: 'tagAppendType', + type: 'options', + options: [ + { name: 'Upload', value: 'upload' }, + { name: 'Private', value: 'private' }, + { name: 'Authenticated', value: 'authenticated' }, + { name: 'Fetch', value: 'fetch' }, + ], + default: 'upload', + description: + 'The "type" property of the asset (as returned by Cloudinary on the asset object). Must match — public_ids are scoped to (resource_type, type), so an asset stored as "authenticated" is invisible to the default "upload" lookup.', + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + tagMode: ['append'], + }, + }, + }, + { + displayName: 'Public ID(s)', + name: 'tagAppendPublicIds', + type: 'string', + default: '', + description: + 'Asset(s) to tag, by public ID. Paste a single public_id, several separated by commas, or wire up an expression — both single values and arrays from expressions are accepted.', + hint: 'A single public_id is fine — no commas needed. Expressions returning an array also work.', + placeholder: 'docs/strawberry or docs/strawberry, docs/owl', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + tagMode: ['append'], + }, + }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'A comma-separated list of tag names to assign to the asset', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + }, + }, + }, + { + displayName: 'Structured Metadata', + name: 'structuredMetadata', + type: 'json', + default: '{}', + description: + 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', + required: true, + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateMetadata'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateMetadata'], + }, + }, + default: {}, + options: [ + { + displayName: 'Invalidate CDN', + name: 'invalidate', + type: 'boolean', + default: false, + description: 'Whether to invalidate CDN cache copies of the asset', + }, + ], + }, + { + displayName: 'Update Fields', + name: 'updateOptions', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + resource: ['updateAsset'], + operation: ['updateTags'], + tagMode: ['set'], + }, + }, + default: {}, + options: [ + { + displayName: 'Invalidate CDN', + name: 'invalidate', + type: 'boolean', + default: false, + description: 'Whether to invalidate CDN cache copies of the asset', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/descriptions/upload.fields.ts b/nodes/Cloudinary/descriptions/upload.fields.ts new file mode 100644 index 0000000..a66387e --- /dev/null +++ b/nodes/Cloudinary/descriptions/upload.fields.ts @@ -0,0 +1,191 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const uploadFields: INodeProperties[] = [ + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + description: 'URL of the image to upload', + required: true, + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadUrl'], + }, + }, + }, + { + displayName: 'Resource Type', + name: 'resource_type', + type: 'options', + options: [ + { + name: 'Image', + value: 'image', + }, + { + name: 'Video', + value: 'video', + }, + { + name: 'Raw', + value: 'raw', + }, + ], + default: 'image', + description: 'The type of asset to upload', + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadUrl'], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadUrl'], + }, + }, + default: {}, + options: [ + { + displayName: 'Folder', + name: 'folder', + type: 'string', + default: '', + description: 'Folder name where the asset will be stored', + }, + { + displayName: 'Public ID', + name: 'public_id', + type: 'string', + default: '', + description: 'The public ID of the resource', + }, + { + displayName: 'Structured Metadata', + name: 'metadata', + type: 'json', + default: '{}', + description: + 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'A comma-separated list of tag names to assign to the asset', + }, + { + displayName: 'Upload Preset', + name: 'upload_preset', + type: 'string', + default: '', + description: 'Name of an upload preset that you defined for your Cloudinary account', + }, + ], + }, + { + displayName: 'File', + name: 'file', + type: 'string', + typeOptions: { + propertyType: 'binary', + }, + default: 'data', + description: 'The file to upload', + required: true, + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadFile'], + }, + }, + }, + { + displayName: 'Resource Type', + name: 'resource_type_file', + type: 'options', + options: [ + { + name: 'Image', + value: 'image', + }, + { + name: 'Video', + value: 'video', + }, + { + name: 'Raw', + value: 'raw', + }, + ], + default: 'image', + description: 'The type of asset to upload', + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadFile'], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFieldsFile', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['upload'], + operation: ['uploadFile'], + }, + }, + default: {}, + options: [ + { + displayName: 'Folder', + name: 'folder', + type: 'string', + default: '', + description: 'Folder name where the asset will be stored', + }, + { + displayName: 'Public ID', + name: 'public_id', + type: 'string', + default: '', + description: 'The public ID of the resource', + }, + { + displayName: 'Structured Metadata', + name: 'metadata', + type: 'json', + default: '{}', + description: + 'Structured metadata to attach to the asset as JSON. Example: {"field1": "value1", "field2": "value2"}.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'A comma-separated list of tag names to assign to the asset', + }, + { + displayName: 'Upload Preset', + name: 'upload_preset', + type: 'string', + default: '', + description: 'Name of an upload preset that you defined for your Cloudinary account', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/descriptions/widget.fields.ts b/nodes/Cloudinary/descriptions/widget.fields.ts new file mode 100644 index 0000000..ed92e60 --- /dev/null +++ b/nodes/Cloudinary/descriptions/widget.fields.ts @@ -0,0 +1,256 @@ +import { INodeProperties } from 'n8n-workflow'; + +// Widget resource — Cloudinary embeds/widgets that build a URL with no API call +// (the "third flow" — see CLAUDE.md). The Video Player op emits an iframe embed URL +// and a self-hosted `player_config` JSON. The shared Public ID + Type identity fields +// live in transform.fields.ts (gated on resource:['transform','widget']) so they are +// not duplicated here. + +export const widgetFields: INodeProperties[] = [ + { + displayName: 'Autoplay Mode', + name: 'playerAutoplayMode', + type: 'options', + options: [ + { name: 'Never', value: '', description: 'Do not autoplay (player default)' }, + { name: 'Always', value: 'always', description: 'Autoplay as soon as the player loads' }, + { name: 'On Scroll', value: 'on-scroll', description: 'Autoplay when the player scrolls into view' }, + ], + default: '', + description: 'When the video should start playing automatically. Most browsers require the player to be muted for autoplay to work.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Muted', + name: 'playerMuted', + type: 'boolean', + default: false, + description: 'Whether the player starts muted', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Loop', + name: 'playerLoop', + type: 'boolean', + default: false, + description: 'Whether the video restarts from the beginning when it ends', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Source Types', + name: 'playerSourceTypes', + type: 'multiOptions', + options: [ + { name: 'Auto', value: 'auto', description: 'Let the player pick the best available format' }, + { name: 'HLS', value: 'hls', description: 'HTTP Live Streaming (adaptive bitrate)' }, + { name: 'MP4', value: 'mp4', description: 'Progressive MP4' }, + { name: 'MPEG-DASH', value: 'dash', description: 'Dynamic Adaptive Streaming over HTTP' }, + { name: 'Ogg', value: 'ogg', description: 'Progressive Ogg' }, + { name: 'WebM', value: 'webm', description: 'Progressive WebM' }, + ], + default: [], + description: 'Preferred delivery formats in fallback order. Leave empty to use the player\'s default format selection.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Poster', + name: 'playerPoster', + type: 'string', + default: '', + description: 'URL of the still image shown before playback starts. Leave empty to use a frame from the video.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Transformation', + name: 'playerTransformation', + type: 'string', + default: '', + description: 'Cloudinary transformation string applied to the video source in the self-hosted player (player_config output). Note: not included in the iframe embed URL — the Cloudinary player iframe does not support video-level transformations via URL parameters.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Fluid', + name: 'playerFluid', + type: 'boolean', + default: false, + description: 'Whether the player resizes responsively to fill its container. When on, Width and Height are replaced by an aspect-ratio CSS style.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Width', + name: 'playerWidth', + type: 'number', + default: 0, + description: 'Player width in pixels. Leave 0 for the default (640 px).', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'], playerFluid: [false] }, + }, + }, + { + displayName: 'Height', + name: 'playerHeight', + type: 'number', + default: 0, + description: 'Player height in pixels. Leave 0 for the default (360 px).', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'], playerFluid: [false] }, + }, + }, + { + displayName: 'Aspect Ratio', + name: 'playerAspectRatio', + type: 'options', + options: [ + { name: '1:1', value: '1:1' }, + { name: '16:9', value: '16:9' }, + { name: '9:16', value: '9:16' }, + { name: 'Default', value: '' }, + ], + default: '', + description: 'Player aspect ratio. Leave unset to use the video\'s natural dimensions.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Skin', + name: 'playerSkin', + type: 'options', + options: [ + { name: 'Dark', value: 'dark' }, + { name: 'Light', value: 'light' }, + ], + default: 'dark', + description: 'Player theme', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Base Color', + name: 'playerBaseColor', + type: 'color', + default: '', + description: 'Player base (background) color, as a hex value', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Accent Color', + name: 'playerAccentColor', + type: 'color', + default: '', + description: 'Player accent (highlight) color, as a hex value', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Text Color', + name: 'playerTextColor', + type: 'color', + default: '', + description: 'Player text color, as a hex value', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Font Face', + name: 'playerFontFace', + type: 'string', + default: '', + description: 'The font applied to player text elements (titles, descriptions, recommendations, time counter). Accepts a Google Font name.', + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + }, + { + displayName: 'Advanced Player Options', + name: 'playerAdvancedOptions', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { resource: ['widget'], operation: ['videoPlayer'] }, + }, + options: [ + { + displayName: 'Big Play Button', + name: 'bigPlayButton', + type: 'boolean', + default: true, + description: 'Whether to show a larger central play button when the video is paused', + }, + { + displayName: 'Chapters Button', + name: 'chaptersButton', + type: 'boolean', + default: false, + description: 'Whether to show the chapters button, which opens a list of chapters', + }, + { + displayName: 'Floating When Not Visible', + name: 'floatingWhenNotVisible', + type: 'options', + options: [ + { name: 'Disabled', value: 'disabled' }, + { name: 'Left', value: 'left' }, + { name: 'Right', value: 'right' }, + ], + default: 'disabled', + description: 'Whether the player should float into a mini-player corner when scrolled out of view', + }, + { + displayName: 'HDR', + name: 'hdr', + type: 'boolean', + default: false, + description: 'Whether to deliver HDR video when the viewer\'s device and browser support it', + }, + { + displayName: 'Picture In Picture Toggle', + name: 'pictureInPictureToggle', + type: 'boolean', + default: false, + description: 'Whether to show the picture-in-picture toggle, letting users view the video in a floating window', + }, + { + displayName: 'Plays Inline', + name: 'playsinline', + type: 'boolean', + default: false, + description: 'Whether to prevent the player from entering fullscreen automatically on iOS when playback starts', + }, + { + displayName: 'Seek Thumbnails', + name: 'seekThumbnails', + type: 'boolean', + default: true, + description: 'Whether to show thumbnail previews when seeking with the seek bar', + }, + { + displayName: 'Show Controls', + name: 'controls', + type: 'boolean', + default: true, + description: 'Whether to show the built-in playback controls (play, pause, volume, fullscreen, etc.)', + }, + ], + }, +]; diff --git a/nodes/Cloudinary/operations/admin/adminGet.test.ts b/nodes/Cloudinary/operations/admin/adminGet.test.ts new file mode 100644 index 0000000..efbb981 --- /dev/null +++ b/nodes/Cloudinary/operations/admin/adminGet.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { getTags } from './getTags'; +import { getMetadataFields } from './getMetadataFields'; +import { makeCtx, lastRequest, testCreds } from '../testHelpers'; + +describe('getTags handler', () => { + it('GETs the tags endpoint for the resource type with Basic auth', async () => { + const { ctx, http } = makeCtx({ + params: { getTagsResourceType: 'image', tagsPrefix: '', tagsMaxResults: 100 }, + }); + + await getTags(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('GET'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/tags/image'); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + }); + + it('omits the prefix query param when prefix is empty', async () => { + const { ctx, http } = makeCtx({ + params: { getTagsResourceType: 'image', tagsPrefix: '', tagsMaxResults: 100 }, + }); + + await getTags(ctx, 0, testCreds); + + expect(lastRequest(http).qs).toEqual({ max_results: 100 }); + }); + + it('includes the prefix query param when provided', async () => { + const { ctx, http } = makeCtx({ + params: { getTagsResourceType: 'video', tagsPrefix: 'promo', tagsMaxResults: 50 }, + }); + + await getTags(ctx, 0, testCreds); + + expect(lastRequest(http).qs).toEqual({ prefix: 'promo', max_results: 50 }); + }); +}); + +describe('getMetadataFields handler', () => { + it('GETs the metadata_fields endpoint with Basic auth', async () => { + const { ctx, http } = makeCtx(); + + await getMetadataFields(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('GET'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/metadata_fields'); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + }); +}); diff --git a/nodes/Cloudinary/operations/admin/getMetadataFields.ts b/nodes/Cloudinary/operations/admin/getMetadataFields.ts new file mode 100644 index 0000000..6c92bd4 --- /dev/null +++ b/nodes/Cloudinary/operations/admin/getMetadataFields.ts @@ -0,0 +1,19 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const getMetadataFields: OperationHandler = async (ctx, i, creds) => { + const options: IHttpRequestOptions = { + method: 'GET', + url: `https://api.cloudinary.com/v1_1/${creds.cloudName}/metadata_fields`, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/admin/getTags.ts b/nodes/Cloudinary/operations/admin/getTags.ts new file mode 100644 index 0000000..2b85599 --- /dev/null +++ b/nodes/Cloudinary/operations/admin/getTags.ts @@ -0,0 +1,32 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const getTags: OperationHandler = async (ctx, i, creds) => { + const resourceType = ctx.getNodeParameter('getTagsResourceType', i) as string; + const prefix = ctx.getNodeParameter('tagsPrefix', i, '') as string; + const maxResults = ctx.getNodeParameter('tagsMaxResults', i, 100) as number; + + const queryParams: IDataObject = {}; + if (prefix) { + queryParams.prefix = prefix; + } + if (maxResults) { + queryParams.max_results = maxResults; + } + + const options: IHttpRequestOptions = { + method: 'GET', + url: `https://api.cloudinary.com/v1_1/${creds.cloudName}/tags/${resourceType}`, + qs: queryParams, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/admin/search.test.ts b/nodes/Cloudinary/operations/admin/search.test.ts new file mode 100644 index 0000000..9da3f33 --- /dev/null +++ b/nodes/Cloudinary/operations/admin/search.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { search } from './search'; +import { makeCtx, lastRequest, requestAt, testCreds } from '../testHelpers'; + +const params = (overrides: Record = {}) => ({ + searchExpression: 'tags=cat', + searchResourceTypes: ['image'], + searchReturnAll: false, + searchMaxResults: 50, + searchAdditionalFields: {}, + ...overrides, +}); + +describe('search handler', () => { + it('POSTs to the search endpoint with Basic auth and json:true', async () => { + const { ctx, http } = makeCtx({ params: params() }); + http.mockResolvedValue({ resources: [{ public_id: 'a' }] }); + + await search(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('POST'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/resources/search'); + expect(req.json).toBe(true); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + }); + + it('injects a resource_type clause into the expression', async () => { + const { ctx, http } = makeCtx({ params: params({ searchResourceTypes: ['video'] }) }); + http.mockResolvedValue({ resources: [] }); + + await search(ctx, 0, testCreds); + + expect((lastRequest(http).body as IDataObject).expression).toBe( + '(tags=cat) AND resource_type:video', + ); + }); + + it('returns the flattened resources array', async () => { + const { ctx, http } = makeCtx({ params: params() }); + http.mockResolvedValue({ resources: [{ public_id: 'a' }, { public_id: 'b' }] }); + + const result = await search(ctx, 0, testCreds); + + expect(result).toEqual([{ public_id: 'a' }, { public_id: 'b' }]); + }); + + it('does not paginate when returnAll is false even if a cursor is returned', async () => { + const { ctx, http } = makeCtx({ params: params({ searchMaxResults: 50 }) }); + // First (and only) page fills the requested count, so the loop stops. + http.mockResolvedValue({ + resources: Array.from({ length: 50 }, (_, k) => ({ public_id: `p${k}` })), + next_cursor: 'more', + }); + + const result = await search(ctx, 0, testCreds); + + expect(http).toHaveBeenCalledTimes(1); + expect(result).toHaveLength(50); + }); + + it('follows next_cursor across pages when returnAll is true', async () => { + const { ctx, http } = makeCtx({ params: params({ searchReturnAll: true }) }); + http + .mockResolvedValueOnce({ resources: [{ public_id: 'a' }], next_cursor: 'cur1' }) + .mockResolvedValueOnce({ resources: [{ public_id: 'b' }] }); + + const result = await search(ctx, 0, testCreds); + + expect(http).toHaveBeenCalledTimes(2); + // Page size is capped at 500 for returnAll, and the cursor is threaded through. + expect((requestAt(http, 0).body as IDataObject).max_results).toBe(500); + expect((requestAt(http, 1).body as IDataObject).next_cursor).toBe('cur1'); + expect(result).toEqual([{ public_id: 'a' }, { public_id: 'b' }]); + }); + + it('maps a 429 rate-limit error to a helpful NodeOperationError', async () => { + const { ctx, http } = makeCtx({ params: params() }); + http.mockRejectedValue({ httpCode: 429, response: { headers: { 'retry-after': '60' } } }); + + await expect(search(ctx, 0, testCreds)).rejects.toThrow( + 'Cloudinary Search rate limit exceeded', + ); + }); + + it('maps a 400 query error to an Invalid search expression error', async () => { + const { ctx, http } = makeCtx({ params: params() }); + http.mockRejectedValue({ + httpCode: 400, + response: { body: { error: { message: 'Query Error (at position 5)' } } }, + }); + + await expect(search(ctx, 0, testCreds)).rejects.toThrow('Invalid search expression'); + }); + + it('surfaces a generic Cloudinary message for other errors', async () => { + const { ctx, http } = makeCtx({ params: params() }); + http.mockRejectedValue({ + httpCode: 500, + response: { body: { error: { message: 'Internal failure' } } }, + }); + + await expect(search(ctx, 0, testCreds)).rejects.toThrow('Cloudinary search failed'); + }); +}); diff --git a/nodes/Cloudinary/operations/admin/search.ts b/nodes/Cloudinary/operations/admin/search.ts new file mode 100644 index 0000000..2cdb68e --- /dev/null +++ b/nodes/Cloudinary/operations/admin/search.ts @@ -0,0 +1,104 @@ +import { IDataObject, IHttpRequestOptions, NodeOperationError } from 'n8n-workflow'; +import { basicAuth, buildSearchExpression, extractCloudinaryError, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const search: OperationHandler = async (ctx, i, creds) => { + const expressionInput = ctx.getNodeParameter('searchExpression', i, '') as string; + const resourceTypes = ctx.getNodeParameter('searchResourceTypes', i, ['image']) as string[]; + const returnAll = ctx.getNodeParameter('searchReturnAll', i, false) as boolean; + const additionalFields = ctx.getNodeParameter('searchAdditionalFields', i, {}) as IDataObject; + + const perPage = returnAll ? 500 : (ctx.getNodeParameter('searchMaxResults', i, 50) as number); + const expression = buildSearchExpression(expressionInput, resourceTypes); + + const baseBody: IDataObject = {}; + if (expression) { + baseBody.expression = expression; + } + if (additionalFields.sortField) { + const direction = (additionalFields.sortDirection as string) || 'desc'; + baseBody.sort_by = [{ [additionalFields.sortField as string]: direction }]; + } + if (Array.isArray(additionalFields.with_field) && additionalFields.with_field.length > 0) { + baseBody.with_field = additionalFields.with_field; + } + + const searchUrl = `https://api.cloudinary.com/v1_1/${creds.cloudName}/resources/search`; + let nextCursor: string | undefined = additionalFields.next_cursor as string | undefined; + let remaining = perPage; + let pageCount = 0; + const results: IDataObject[] = []; + + do { + const pageSize = returnAll ? 500 : Math.min(remaining, 500); + const body: IDataObject = { + ...baseBody, + max_results: pageSize, + }; + if (nextCursor) { + body.next_cursor = nextCursor; + } + + const options: IHttpRequestOptions = { + method: 'POST', + url: searchUrl, + body, + json: true, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + let response: IDataObject; + try { + response = (await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + )) as IDataObject; + } catch (error) { + const { status, retryAfter, cloudinaryMessage } = extractCloudinaryError(error); + + if (status === 420 || status === 429 || status === '420' || status === '429') { + const suffix = retryAfter ? ` Retry after ${retryAfter} seconds.` : ''; + throw new NodeOperationError(ctx.getNode(), 'Cloudinary Search rate limit exceeded', { + description: `The Search endpoint has a lower hourly limit than other Admin endpoints (~100/hour on free plans).${suffix} Narrow your expression or run less frequently.`, + itemIndex: i, + }); + } + + if ( + (status === 400 || status === '400') && + cloudinaryMessage?.toLowerCase().startsWith('query error') + ) { + throw new NodeOperationError(ctx.getNode(), 'Invalid search expression', { + description: `${cloudinaryMessage}. Check for unclosed quotes, unbalanced parentheses, or typos in field names. See the Expression syntax link in the field hint.`, + itemIndex: i, + }); + } + + if (cloudinaryMessage) { + throw new NodeOperationError(ctx.getNode(), 'Cloudinary search failed', { + description: cloudinaryMessage, + itemIndex: i, + }); + } + throw error; + } + + const resources = Array.isArray(response.resources) + ? (response.resources as IDataObject[]) + : []; + results.push(...resources); + + nextCursor = response.next_cursor as string | undefined; + pageCount += 1; + if (!returnAll) { + remaining -= resources.length; + if (remaining <= 0) break; + } + // Safety cap to avoid runaway loops on misbehaving cursors + if (pageCount > 1000) break; + } while (returnAll && nextCursor); + + return results; +}; diff --git a/nodes/Cloudinary/operations/asset/asset.test.ts b/nodes/Cloudinary/operations/asset/asset.test.ts new file mode 100644 index 0000000..81a1028 --- /dev/null +++ b/nodes/Cloudinary/operations/asset/asset.test.ts @@ -0,0 +1,114 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { updateTags } from './updateTags'; +import { updateMetadata } from './updateMetadata'; +import { makeCtx, lastRequest, testCreds } from '../testHelpers'; + +const ASSET_ID_URL = 'https://api.cloudinary.com/v1_1/demo/resources/abc123'; + +describe('asset:updateTags handler', () => { + it('PUTs the asset_id URL with body { tags } and Basic auth', async () => { + const { ctx, http } = makeCtx({ + params: { assetId: 'abc123', tags: 'cat,dog' }, + }); + + await updateTags(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('PUT'); + expect(req.url).toBe(ASSET_ID_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + expect(req.body).toEqual({ tags: 'cat,dog' }); + expect((req.body as IDataObject).signature).toBeUndefined(); + }); + + it('spreads updateOptions into the body', async () => { + const { ctx, http } = makeCtx({ + params: { assetId: 'abc123', tags: 'cat', updateOptions: { invalidate: true } }, + }); + + await updateTags(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.body).toEqual({ tags: 'cat', invalidate: true }); + }); +}); + +describe('asset:updateTags append mode', () => { + it('POSTs to /:resource_type/tags with command=add, signed body, CSV joined ids', async () => { + const { ctx, http } = makeCtx({ + params: { + tagMode: 'append', + tagAppendPublicIds: 'docs/a, docs/b', + tagAppendResourceType: 'image', + tagAppendType: 'upload', + tags: 'cat,dog', + }, + }); + + await updateTags(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('POST'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/image/tags'); + expect(req.headers?.['Content-Type']).toBe('application/x-www-form-urlencoded'); + expect(req.auth).toBeUndefined(); + const body = req.body as IDataObject; + expect(body.command).toBe('add'); + expect(body.public_ids).toBe('docs/a,docs/b'); + expect(body.tag).toBe('cat,dog'); + expect(body.type).toBe('upload'); + expect(body.api_key).toBe(testCreds.apiKey); + expect(typeof body.signature).toBe('string'); + expect((body.signature as string).length).toBeGreaterThan(0); + }); + + it('includes delivery type in the signed body for authenticated assets', async () => { + const { ctx, http } = makeCtx({ + params: { + tagMode: 'append', + tagAppendPublicIds: 'secret/asset', + tagAppendResourceType: 'image', + tagAppendType: 'authenticated', + tags: 'classified', + }, + }); + + await updateTags(ctx, 0, testCreds); + + const body = lastRequest(http).body as IDataObject; + expect(body.type).toBe('authenticated'); + }); + + it('routes video resource type to /video/tags', async () => { + const { ctx, http } = makeCtx({ + params: { + tagMode: 'append', + tagAppendPublicIds: 'clip', + tagAppendResourceType: 'video', + tagAppendType: 'upload', + tags: 'trailer', + }, + }); + + await updateTags(ctx, 0, testCreds); + + expect(lastRequest(http).url).toBe('https://api.cloudinary.com/v1_1/demo/video/tags'); + }); +}); + +describe('asset:updateMetadata handler', () => { + it('PUTs the asset_id URL with pipe-string metadata and Basic auth', async () => { + const { ctx, http } = makeCtx({ + params: { assetId: 'abc123', structuredMetadata: '{"color":"red"}' }, + }); + + await updateMetadata(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('PUT'); + expect(req.url).toBe(ASSET_ID_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + expect((req.body as IDataObject).metadata).toBe('color=red'); + }); +}); diff --git a/nodes/Cloudinary/operations/asset/updateMetadata.ts b/nodes/Cloudinary/operations/asset/updateMetadata.ts new file mode 100644 index 0000000..33c9861 --- /dev/null +++ b/nodes/Cloudinary/operations/asset/updateMetadata.ts @@ -0,0 +1,34 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { + basicAuth, + buildResourceByAssetIdUrl, + jsonHeaders, + metadataToPipeString, +} from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const updateMetadata: OperationHandler = async (ctx, i, creds) => { + const assetId = ctx.getNodeParameter('assetId', i) as string; + const structuredMetadata = ctx.getNodeParameter('structuredMetadata', i) as string; + const updateOptions = ctx.getNodeParameter('updateOptions', i, {}) as IDataObject; + + const body: IDataObject = { + metadata: metadataToPipeString(structuredMetadata), + ...updateOptions, + }; + + const options: IHttpRequestOptions = { + method: 'PUT', + url: buildResourceByAssetIdUrl(creds.cloudName, assetId), + body, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/asset/updateTags.ts b/nodes/Cloudinary/operations/asset/updateTags.ts new file mode 100644 index 0000000..05dcf2b --- /dev/null +++ b/nodes/Cloudinary/operations/asset/updateTags.ts @@ -0,0 +1,35 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, buildResourceByAssetIdUrl, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; +import { appendTags } from '../tagAppend'; + +export const updateTags: OperationHandler = async (ctx, i, creds) => { + const tagMode = ctx.getNodeParameter('tagMode', i, 'set') as string; + if (tagMode === 'append') { + return appendTags(ctx, i, creds); + } + + const assetId = ctx.getNodeParameter('assetId', i) as string; + const tags = ctx.getNodeParameter('tags', i) as string; + const updateOptions = ctx.getNodeParameter('updateOptions', i, {}) as IDataObject; + + const body: IDataObject = { + tags, + ...updateOptions, + }; + + const options: IHttpRequestOptions = { + method: 'PUT', + url: buildResourceByAssetIdUrl(creds.cloudName, assetId), + body, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/index.ts b/nodes/Cloudinary/operations/index.ts new file mode 100644 index 0000000..8cb2a81 --- /dev/null +++ b/nodes/Cloudinary/operations/index.ts @@ -0,0 +1,50 @@ +import { OperationHandler } from './types'; +import { uploadUrl } from './upload/uploadUrl'; +import { uploadFile } from './upload/uploadFile'; +import { deleteAssets } from './updateAsset/deleteAssets'; +import { getAsset } from './updateAsset/getAsset'; +import { updateTags } from './updateAsset/updateTags'; +import { updateMetadata } from './updateAsset/updateMetadata'; +import { updateTags as assetUpdateTags } from './asset/updateTags'; +import { updateMetadata as assetUpdateMetadata } from './asset/updateMetadata'; +import { search } from './admin/search'; +import { getTags } from './admin/getTags'; +import { getMetadataFields } from './admin/getMetadataFields'; +import { optimizeImage } from './transform/optimizeImage'; +import { resizeImage } from './transform/resizeImage'; +import { cropImage } from './transform/cropImage'; +import { convertImage } from './transform/convertImage'; +import { optimizeVideo } from './transform/optimizeVideo'; +import { trimVideo } from './transform/trimVideo'; +import { videoThumbnail } from './transform/videoThumbnail'; +import { customTransformation } from './transform/customTransformation'; +import { multiStep } from './transform/multiStep'; +import { videoPlayer } from './widget/videoPlayer'; + +/** + * Maps `${resource}:${operation}` to its handler. Add a new operation by + * dropping a file in operations// and registering it here. + */ +export const operationHandlers: Record = { + 'upload:uploadUrl': uploadUrl, + 'upload:uploadFile': uploadFile, + 'updateAsset:updateTags': updateTags, + 'updateAsset:updateMetadata': updateMetadata, + 'asset:getAsset': getAsset, + 'asset:deleteAssets': deleteAssets, + 'asset:updateTags': assetUpdateTags, + 'asset:updateMetadata': assetUpdateMetadata, + 'asset:search': search, + 'admin:getTags': getTags, + 'admin:getMetadataFields': getMetadataFields, + 'transform:optimizeImage': optimizeImage, + 'transform:resizeImage': resizeImage, + 'transform:cropImage': cropImage, + 'transform:convertImage': convertImage, + 'transform:optimizeVideo': optimizeVideo, + 'transform:trimVideo': trimVideo, + 'transform:videoThumbnail': videoThumbnail, + 'transform:customTransformation': customTransformation, + 'transform:combineTransformations': multiStep, + 'widget:videoPlayer': videoPlayer, +}; diff --git a/nodes/Cloudinary/operations/tagAppend.ts b/nodes/Cloudinary/operations/tagAppend.ts new file mode 100644 index 0000000..b962e6c --- /dev/null +++ b/nodes/Cloudinary/operations/tagAppend.ts @@ -0,0 +1,66 @@ +import { IDataObject, IHttpRequestOptions, NodeOperationError } from 'n8n-workflow'; +import type { IExecuteFunctions } from 'n8n-workflow'; +import { + buildTagsActionUrl, + generateCloudinarySignature, + splitCsvIds, + USER_AGENT, +} from '../cloudinary.utils'; +import { CREDENTIAL_TYPE, CloudinaryCredentials } from './types'; + +/** + * Shared `Append Tags` request used by both `asset:updateTags` and + * `updateAsset:updateTags` when the user selects `tagMode: 'append'`. Hits the + * Upload API tag-action endpoint (`POST /:resource_type/tags`) with `command=add` + * — a signed flow (api_key + timestamp + signature), not Basic auth, and keyed + * on public_id (no asset_id variant exists). + */ +export const appendTags = async ( + ctx: IExecuteFunctions, + i: number, + creds: CloudinaryCredentials, +): Promise => { + const rawPublicIds = ctx.getNodeParameter('tagAppendPublicIds', i) as string | string[]; + const resourceType = ctx.getNodeParameter('tagAppendResourceType', i, 'image') as string; + const type = ctx.getNodeParameter('tagAppendType', i, 'upload') as string; + const tags = ctx.getNodeParameter('tags', i) as string; + + const publicIds = Array.isArray(rawPublicIds) + ? rawPublicIds.map((s) => String(s).trim()).filter((s) => s.length > 0) + : splitCsvIds(rawPublicIds); + + if (publicIds.length === 0) { + throw new NodeOperationError(ctx.getNode(), 'No public IDs provided', { + description: 'Provide at least one public_id (comma-separated for many).', + itemIndex: i, + }); + } + + const timestamp = Math.round(new Date().getTime() / 1000); + const params: IDataObject = { + command: 'add', + public_ids: publicIds.join(','), + tag: tags, + type, + timestamp, + api_key: creds.apiKey, + }; + params.signature = generateCloudinarySignature(params, creds.apiSecret); + + const options: IHttpRequestOptions = { + method: 'POST', + url: buildTagsActionUrl(creds.cloudName, resourceType), + body: params, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + }, + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/testHelpers.ts b/nodes/Cloudinary/operations/testHelpers.ts new file mode 100644 index 0000000..2ed67df --- /dev/null +++ b/nodes/Cloudinary/operations/testHelpers.ts @@ -0,0 +1,70 @@ +import { vi, expect } from 'vitest'; +import type { IDataObject, IExecuteFunctions, IHttpRequestOptions } from 'n8n-workflow'; +import { CREDENTIAL_TYPE, CloudinaryCredentials } from './types'; + +export const testCreds: CloudinaryCredentials = { + cloudName: 'demo', + apiKey: 'key123', + apiSecret: 'secret123', +}; + +export interface MockCtxOptions { + /** Node-parameter map. Reads of unknown names fall back to getNodeParameter's default arg. */ + params?: Record; + /** Return value of assertBinaryData. */ + binary?: { fileName?: string; mimeType?: string }; + /** Buffer returned by getBinaryDataBuffer. */ + buffer?: Buffer; + /** Input items seen by execute() via getInputData. */ + items?: IDataObject[]; + /** What continueOnFail() reports. */ + continueOnFail?: boolean; +} + +/** + * Builds a mock IExecuteFunctions exposing only the surface the handlers and + * execute() touch. The returned `http` spy resolves to {} by default; tests + * configure it with mockResolvedValue / mockResolvedValueOnce / mockRejectedValue + * and then assert the request contract via lastRequest/requestAt. + */ +export function makeCtx(options: MockCtxOptions = {}) { + const { + params = {}, + binary = { fileName: 'cat.png', mimeType: 'image/png' }, + buffer = Buffer.from('PNGDATA'), + continueOnFail = false, + } = options; + + const http = vi.fn().mockResolvedValue({}); + const ctx = { + getInputData: vi.fn(() => options.items ?? [{ json: {} }]), + getCredentials: vi.fn(async () => ({ ...testCreds })), + getNodeParameter: vi.fn((name: string, _i: number, fallback?: unknown) => + name in params ? params[name] : fallback, + ), + getNode: vi.fn(() => ({ name: 'Cloudinary', type: 'cloudinary' })), + continueOnFail: vi.fn(() => continueOnFail), + helpers: { + assertBinaryData: vi.fn(() => binary), + getBinaryDataBuffer: vi.fn(async () => buffer), + httpRequestWithAuthentication: http, + }, + } as unknown as IExecuteFunctions; + + return { ctx, http }; +} + +/** + * Extracts the IHttpRequestOptions from the Nth call to + * httpRequestWithAuthentication.call(ctx, TYPE, options). Because the handler + * uses `.call`, `this` is NOT in the recorded args — they are [TYPE, options]. + */ +export function requestAt(http: ReturnType, callIndex: number): IHttpRequestOptions { + const [type, options] = http.mock.calls[callIndex]; + expect(type).toBe(CREDENTIAL_TYPE); + return options as IHttpRequestOptions; +} + +export function lastRequest(http: ReturnType): IHttpRequestOptions { + return requestAt(http, http.mock.calls.length - 1); +} diff --git a/nodes/Cloudinary/operations/transform/convertImage.ts b/nodes/Cloudinary/operations/transform/convertImage.ts new file mode 100644 index 0000000..120ad26 --- /dev/null +++ b/nodes/Cloudinary/operations/transform/convertImage.ts @@ -0,0 +1,22 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildComponents, buildTransformResult, convertComponents, readTransformInput } from './shared'; + +/** Convert an image to another format. Emits `f_` and delivers with `.`. */ +export const convertImage: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const format = ctx.getNodeParameter('convertFormat', i, 'webp') as string; + + const components = buildComponents(ctx, i, () => convertComponents(format)); + + return [ + buildTransformResult(creds, { + resourceType: 'image', + type: deliveryType, + transformation: joinTransformation(components), + publicId, + format, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/cropImage.ts b/nodes/Cloudinary/operations/transform/cropImage.ts new file mode 100644 index 0000000..5dfe932 --- /dev/null +++ b/nodes/Cloudinary/operations/transform/cropImage.ts @@ -0,0 +1,40 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildComponents, buildTransformResult, cropComponents, readTransformInput } from './shared'; + +/** + * Crop an image to fixed dimensions or an aspect ratio. + * - Default: `c_fill,g_,w_,h_` (crops away pixels). + * - Generative Fill: `b_gen_fill[:prompt_...],c_pad,...` — extends/pads the canvas + * with AI-generated content instead of cropping (a generative AI effect). + */ +export const cropImage: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const cropBy = ctx.getNodeParameter('cropBy', i, 'dimensions') as string; + const genFill = ctx.getNodeParameter('cropGenerativeFill', i, false) as boolean; + const focus = ctx.getNodeParameter('cropFocus', i, 'auto') as string; + const genFillPrompt = ctx.getNodeParameter('cropGenerativeFillPrompt', i, '') as string; + + // Aspect-ratio mode reads its own width field; dimensions mode reads width+height. + const width = + cropBy === 'aspectRatio' + ? (ctx.getNodeParameter('cropAspectWidth', i, 0) as number) + : (ctx.getNodeParameter('cropWidth', i, 0) as number); + const height = cropBy === 'aspectRatio' ? 0 : (ctx.getNodeParameter('cropHeight', i, 0) as number); + const aspectRatio = + cropBy === 'aspectRatio' ? (ctx.getNodeParameter('cropAspectRatio', i, '') as string) : ''; + + const components = buildComponents(ctx, i, () => + cropComponents({ cropBy, width, height, aspectRatio, focus, genFill, genFillPrompt }), + ); + + return [ + buildTransformResult(creds, { + resourceType: 'image', + type: deliveryType, + transformation: joinTransformation(components), + publicId, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/customTransformation.ts b/nodes/Cloudinary/operations/transform/customTransformation.ts new file mode 100644 index 0000000..1ddcbde --- /dev/null +++ b/nodes/Cloudinary/operations/transform/customTransformation.ts @@ -0,0 +1,32 @@ +import { NodeOperationError } from 'n8n-workflow'; +import { OperationHandler } from '../types'; +import { buildTransformResult, readTransformInput } from './shared'; + +/** + * Power-user escape hatch: build a delivery URL from a raw Cloudinary + * transformation string, used verbatim. Resource type is explicit (image/video) + * since a freeform string can target either. + */ +export const customTransformation: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const resourceType = ctx.getNodeParameter('customResourceType', i, 'image') as string; + const transformation = (ctx.getNodeParameter('customTransformationString', i, '') as string).trim(); + const format = (ctx.getNodeParameter('customFormat', i, '') as string).trim(); + + if (!transformation) { + throw new NodeOperationError(ctx.getNode(), 'A transformation string is required', { + itemIndex: i, + }); + } + + return [ + buildTransformResult(creds, { + resourceType, + type: deliveryType, + transformation, + publicId, + format: format || undefined, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/multiStep.ts b/nodes/Cloudinary/operations/transform/multiStep.ts new file mode 100644 index 0000000..eb31fdd --- /dev/null +++ b/nodes/Cloudinary/operations/transform/multiStep.ts @@ -0,0 +1,101 @@ +import { IDataObject, NodeOperationError } from 'n8n-workflow'; +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { + buildComponents, + buildTransformResult, + convertComponents, + cropComponents, + optimizeComponents, + readTransformInput, + resizeComponents, + trimComponents, +} from './shared'; + +/** + * Multi-Step Transformation: compound several steps into a single delivery URL, + * mirroring n8n's built-in Edit Image "Multi Step" operation. The `transformSteps` + * fixedCollection is an ordered, sortable list; each step contributes one or more + * Cloudinary transformation components, which are chained with `/` and applied in + * sequence. No API call is made — this is the "third flow" (pure URL construction). + * + * Each step delegates to the SAME component builder the matching standalone op uses + * (see shared.ts), so the two never drift. `raw` is the escape hatch for any + * component the builders don't cover. + */ +export const multiStep: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const resourceType = ctx.getNodeParameter('multiStepResourceType', i, 'image') as string; + const stepsData = ctx.getNodeParameter('transformSteps', i, {}) as IDataObject; + const steps = (stepsData.step as IDataObject[] | undefined) ?? []; + + if (!steps.length) { + throw new NodeOperationError(ctx.getNode(), 'Add at least one transformation step', { + itemIndex: i, + }); + } + + const components: string[] = []; + // A Convert step sets the delivery format (URL extension); the last one wins. + let format: string | undefined; + + steps.forEach((step, idx) => { + const list = buildComponents( + ctx, + i, + () => stepComponents(step, resourceType), + `Step ${idx + 1}: `, + ); + components.push(...list); + if (step.stepType === 'convert') { + format = String(step.format ?? '').trim() || format; + } + }); + + return [ + buildTransformResult(creds, { + resourceType, + type: deliveryType, + transformation: joinTransformation(components), + publicId, + format, + version, + }), + ]; +}; + +const str = (v: unknown): string => String(v ?? ''); +const num = (v: unknown): number => Number(v) || 0; + +/** Map one step row to its transformation component(s) via the shared builders. */ +function stepComponents(step: IDataObject, resourceType: string): string[] { + switch (step.stepType as string) { + case 'trim': + return trimComponents({ start: str(step.start), end: str(step.end), duration: str(step.duration) }); + case 'resize': + return resizeComponents({ width: num(step.width), height: num(step.height), fit: str(step.fit) || 'limit' }); + case 'crop': { + const cropBy = str(step.cropMode) || 'dimensions'; + return cropComponents({ + cropBy, + width: cropBy === 'aspectRatio' ? num(step.cropAspectWidth) : num(step.cropWidth), + height: num(step.cropHeight), + aspectRatio: str(step.aspectRatio), + focus: str(step.focus) || 'auto', + }); + } + case 'optimize': + return optimizeComponents({ quality: str(step.quality) || 'auto', resourceType }); + case 'convert': + return convertComponents(str(step.format)); + case 'raw': { + const raw = str(step.raw).trim(); + if (!raw) { + throw new Error('Raw step requires a transformation component'); + } + return [raw]; + } + default: + throw new Error(`Unknown step type "${step.stepType}"`); + } +} diff --git a/nodes/Cloudinary/operations/transform/optimizeImage.ts b/nodes/Cloudinary/operations/transform/optimizeImage.ts new file mode 100644 index 0000000..c68d0e8 --- /dev/null +++ b/nodes/Cloudinary/operations/transform/optimizeImage.ts @@ -0,0 +1,21 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildTransformResult, optimizeComponents, readTransformInput } from './shared'; + +/** Optimize an image: auto-format + auto-quality. Emits `f_auto/q_auto[:level]`. */ +export const optimizeImage: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const quality = ctx.getNodeParameter('imageQuality', i, 'auto') as string; + + const transformation = joinTransformation(optimizeComponents({ quality, resourceType: 'image' })); + + return [ + buildTransformResult(creds, { + resourceType: 'image', + type: deliveryType, + transformation, + publicId, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/optimizeVideo.ts b/nodes/Cloudinary/operations/transform/optimizeVideo.ts new file mode 100644 index 0000000..e51eca8 --- /dev/null +++ b/nodes/Cloudinary/operations/transform/optimizeVideo.ts @@ -0,0 +1,24 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildTransformResult, optimizeComponents, readTransformInput } from './shared'; + +/** + * Optimize a video: auto-quality plus `f_auto:video`, which auto-selects both the + * format and the codec for the requesting browser. Emits `f_auto:video/q_auto[:level]`. + */ +export const optimizeVideo: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const quality = ctx.getNodeParameter('videoQuality', i, 'auto') as string; + + const transformation = joinTransformation(optimizeComponents({ quality, resourceType: 'video' })); + + return [ + buildTransformResult(creds, { + resourceType: 'video', + type: deliveryType, + transformation, + publicId, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/resizeImage.ts b/nodes/Cloudinary/operations/transform/resizeImage.ts new file mode 100644 index 0000000..3ef065b --- /dev/null +++ b/nodes/Cloudinary/operations/transform/resizeImage.ts @@ -0,0 +1,23 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildComponents, buildTransformResult, readTransformInput, resizeComponents } from './shared'; + +/** Resize an image to a width and/or height with a fit mode. Emits `c_,w_,h_`. */ +export const resizeImage: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const width = ctx.getNodeParameter('resizeWidth', i, 0) as number; + const height = ctx.getNodeParameter('resizeHeight', i, 0) as number; + const fit = ctx.getNodeParameter('resizeFit', i, 'limit') as string; + + const components = buildComponents(ctx, i, () => resizeComponents({ width, height, fit })); + + return [ + buildTransformResult(creds, { + resourceType: 'image', + type: deliveryType, + transformation: joinTransformation(components), + publicId, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/shared.ts b/nodes/Cloudinary/operations/transform/shared.ts new file mode 100644 index 0000000..5f4b28f --- /dev/null +++ b/nodes/Cloudinary/operations/transform/shared.ts @@ -0,0 +1,261 @@ +import { IDataObject, IExecuteFunctions, NodeOperationError } from 'n8n-workflow'; +import { buildDeliveryUrl } from '../../cloudinary.utils'; +import { CloudinaryCredentials } from '../types'; + +/** + * Identity + delivery options shared by every Transform operation: the public ID + * plus the `Additional Options` collection (delivery type, version). + */ +export interface TransformInput { + publicId: string; + deliveryType: string; + version: string; +} + +export const readTransformInput = (ctx: IExecuteFunctions, i: number): TransformInput => { + const publicId = ctx.getNodeParameter('transformPublicId', i) as string; + const deliveryType = ctx.getNodeParameter('type', i, 'upload') as string; + const additional = ctx.getNodeParameter('transformAdditionalOptions', i, {}) as IDataObject; + return { + publicId, + deliveryType: deliveryType || 'upload', + version: (additional.version as string) || '', + }; +}; + +/** + * Build the standard delivery-URL result JSON every transform handler returns. + * No API call is made — this is the "third flow" (pure URL construction). The + * `url`/`secure_url` shape mirrors Cloudinary's upload response for familiarity. + */ +export const buildTransformResult = ( + creds: CloudinaryCredentials, + params: { + resourceType: string; + type: string; + transformation: string; + publicId: string; + format?: string; + version?: string; + }, +): IDataObject => { + // The format becomes the delivery URL's trailing extension, and is meaningful only + // for stored-asset types: their public_id is opaque, so the extension is what + // selects the delivered representation. The op's explicit format (Convert/Thumbnail) + // wins; otherwise we recover one baked into a dotted public_id (see the "doubled + // extension" note). For fetch/social sources the public_id is a remote URL / + // external id that already carries its own extension, and any conversion rides in + // the transformation (e.g. f_webp) — so we append nothing, because suffixing would + // corrupt the source identifier. This keeps one invariant: `result.format` is set + // iff the URL carries a trailing `.`, and the two always agree. + const effectiveFormat = STORED_ASSET_TYPES.has(params.type) + ? params.format || trailingMediaFormat(params.publicId) + : undefined; + + const secureUrl = buildDeliveryUrl({ + cloudName: creds.cloudName, + resourceType: params.resourceType, + type: params.type, + transformation: params.transformation, + publicId: params.publicId, + format: effectiveFormat || undefined, + version: params.version || undefined, + privateCdn: creds.privateCdn, + secureDistribution: creds.secureDistribution, + }); + + const result: IDataObject = { + secure_url: secureUrl, + resource_type: params.resourceType, + type: params.type, + public_id: params.publicId, + transformation: params.transformation, + }; + if (effectiveFormat) { + result.format = effectiveFormat; + } + if (params.version) { + result.version = params.version; + } + return result; +}; + +/** Map the quality selector value to a `q_auto[:level]` qualifier. */ +export const qualityQualifier = (quality: string): string => + quality === 'auto' ? 'q_auto' : `q_auto:${quality}`; + +// ───────────────────────────────────────────────────────────────────────────── +// Transformation component builders — the single source of truth for the +// transformation each operation emits. A builder is pure (params → component +// list) and throws a plain Error on invalid input; it is shared by the standalone +// transform ops (one builder per op) AND the Multi-Step op (one builder per step), +// so adding/changing transformation logic in one place updates both. Each builder +// returns the component(s) that `joinTransformation` chains with `/`. See the +// "Keep Multi-Step in sync" note in CLAUDE.md. +// ───────────────────────────────────────────────────────────────────────────── + +/** `c_,w_,h_` — resize to a width and/or height. */ +export const resizeComponents = (p: { width: number; height: number; fit: string }): string[] => { + if (!p.width && !p.height) { + throw new Error('Resize requires a width and/or a height'); + } + const qualifiers = [`c_${p.fit}`]; + if (p.width) qualifiers.push(`w_${p.width}`); + if (p.height) qualifiers.push(`h_${p.height}`); + return [qualifiers.join(',')]; +}; + +/** + * `c_fill,g_,w_,h_` (or `ar_`), or `b_gen_fill[:prompt_…],c_pad,…` when + * generative fill is on — crop to dimensions or an aspect ratio. + */ +export const cropComponents = (p: { + cropBy: string; + width: number; + height: number; + aspectRatio: string; + focus: string; + genFill?: boolean; + genFillPrompt?: string; +}): string[] => { + const dimensions: string[] = []; + if (p.cropBy === 'aspectRatio') { + const aspectRatio = p.aspectRatio.trim(); + if (!aspectRatio) { + throw new Error('Crop by aspect ratio requires an aspect ratio (e.g. 16:9)'); + } + dimensions.push(`ar_${aspectRatio}`); + if (p.width) dimensions.push(`w_${p.width}`); + } else { + if (!p.width && !p.height) { + throw new Error('Crop requires a width and/or a height'); + } + if (p.width) dimensions.push(`w_${p.width}`); + if (p.height) dimensions.push(`h_${p.height}`); + } + + let mode: string[]; + if (p.genFill) { + const prompt = (p.genFillPrompt ?? '').trim(); + const background = prompt ? `b_gen_fill:prompt_${encodeURIComponent(prompt)}` : 'b_gen_fill'; + mode = [background, 'c_pad']; + } else { + mode = ['c_fill', `g_${p.focus}`]; + } + return [[...mode, ...dimensions].join(',')]; +}; + +/** `so_,eo_,du_` — trim by any combination of start, end, and duration. */ +export const trimComponents = (p: { start: string; end: string; duration: string }): string[] => { + const qualifiers: string[] = []; + const start = p.start.trim(); + const end = p.end.trim(); + const duration = p.duration.trim(); + if (start) qualifiers.push(`so_${start}`); + if (end) qualifiers.push(`eo_${end}`); + if (duration) qualifiers.push(`du_${duration}`); + if (!qualifiers.length) { + throw new Error('Trim requires at least one of start, end, or duration'); + } + return [qualifiers.join(',')]; +}; + +/** `f_auto[:video]` + `q_auto[:level]` — auto format/codec and quality. */ +export const optimizeComponents = (p: { quality: string; resourceType: string }): string[] => [ + p.resourceType === 'video' ? 'f_auto:video' : 'f_auto', + qualityQualifier(p.quality), +]; + +/** `f_` — convert/deliver as a specific format. */ +export const convertComponents = (format: string): string[] => { + const fmt = format.trim(); + if (!fmt) { + throw new Error('Convert requires a target format'); + } + return [`f_${fmt}`]; +}; + +/** + * Run a component builder, converting its plain validation Error into the + * NodeOperationError the handlers throw (with `itemIndex`, and an optional prefix + * such as `Step 2: ` for Multi-Step). Keeps validation logic in the builders while + * preserving the node-aware errors callers expect. + */ +export const buildComponents = ( + ctx: IExecuteFunctions, + i: number, + build: () => string[], + prefix = '', +): string[] => { + try { + return build(); + } catch (error) { + throw new NodeOperationError(ctx.getNode(), `${prefix}${(error as Error).message}`, { + itemIndex: i, + }); + } +}; + +// ───────────────────────────────────────────────────────────────────────────── +// The "doubled extension" delivery encoding — DO NOT collapse it, it is correct. +// +// A delivery URL like `.../my_image1234.png.png` looks like a duplication bug. It +// is not. In a Cloudinary delivery URL the public_id and the format are two +// independent fields joined with a dot: the public_id is an OPAQUE identifier (the +// dot in `my_image1234.png` is just part of the string, with no filename/extension +// meaning) and the format is a SEPARATE trailing extension that selects the +// delivered representation. Cloudinary does not check whether the id already ends +// in the requested extension — keeping the two decoupled is what lets one asset be +// delivered as .jpg / .webp / .avif by changing only the format while the id stays +// constant. +// +// Consequence for us: when a public_id was stored WITH its extension baked in +// (`my_image1234.png`), the correct URL is `my_image1234.png` + `.` + `png` = +// `my_image1234.png.png`. If we omit the format, Cloudinary parses the id's OWN +// trailing `.png` as the format, looks up `my_image1234` instead, and 404s. +// +// The named transform ops carry no `format` field (only the bare public_id reaches +// us), so we recover the format from the public_id's trailing media extension below +// and let `buildTransformResult` re-append it. `f_auto` still overrides this for +// content negotiation, so optimization is unaffected by the forced extension. +// +// This set is the allow-list of extensions we treat as a recoverable `format`. +// ───────────────────────────────────────────────────────────────────────────── +const MEDIA_FORMAT_EXTENSIONS = new Set([ + // image + 'jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'bmp', 'tiff', 'tif', 'ico', 'svg', 'heic', 'heif', 'jxl', + // video + 'mp4', 'webm', 'mov', 'avi', 'mkv', 'ogv', 'm4v', '3gp', 'wmv', 'mpeg', 'mpg', 'flv', 'm3u8', 'ts', + // audio + 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a', 'aiff', 'wma', + // document + 'pdf', +]); + +/** + * If a public_id's final path segment ends in a known media format extension, + * return that extension (lowercased); otherwise undefined. Folder dots are ignored + * (only the last segment is inspected). + */ +export const trailingMediaFormat = (publicId: string): string | undefined => { + const lastSegment = publicId.slice(publicId.lastIndexOf('/') + 1); + const dot = lastSegment.lastIndexOf('.'); + if (dot <= 0 || dot === lastSegment.length - 1) { + return undefined; + } + const ext = lastSegment.slice(dot + 1).toLowerCase(); + return MEDIA_FORMAT_EXTENSIONS.has(ext) ? ext : undefined; +}; + +/** + * Delivery types whose public_id is a stored Cloudinary asset we own, so the format + * is delivered as the URL's trailing extension (see the "doubled extension" note). + * For `fetch` and social sources (facebook, twitter, gravatar, youtube, …) the + * "public_id" is instead a remote URL or external profile ID whose trailing dotted + * segment is part of the source itself — appending any extension there would corrupt + * it (e.g. a fetch URL would be delivered as `.../fetch/https://example.com/photo.jpg.jpg`, + * or a conversion as `.../fetch/f_webp/https://example.com/photo.webp`). So the format + * suffix is gated to this set; other types carry conversion only via the transformation + * (e.g. `f_webp`) and pass their identifier through untouched. + */ +const STORED_ASSET_TYPES = new Set(['upload', 'private', 'authenticated']); diff --git a/nodes/Cloudinary/operations/transform/transform.test.ts b/nodes/Cloudinary/operations/transform/transform.test.ts new file mode 100644 index 0000000..30c5c4b --- /dev/null +++ b/nodes/Cloudinary/operations/transform/transform.test.ts @@ -0,0 +1,518 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { optimizeImage } from './optimizeImage'; +import { resizeImage } from './resizeImage'; +import { cropImage } from './cropImage'; +import { convertImage } from './convertImage'; +import { optimizeVideo } from './optimizeVideo'; +import { trimVideo } from './trimVideo'; +import { videoThumbnail } from './videoThumbnail'; +import { customTransformation } from './customTransformation'; +import { multiStep } from './multiStep'; +import { trailingMediaFormat } from './shared'; +import { makeCtx, testCreds } from '../testHelpers'; + +// Transform handlers make NO HTTP call — they build a delivery URL and return it. +// So tests assert the returned JSON (secure_url + transformation + identity), +// not a request spy. makeCtx already provides getNodeParameter + getNode, which is +// the entire surface these handlers touch. + +const HOST = 'https://res.cloudinary.com/demo'; + +describe('transform:optimizeImage', () => { + it('emits f_auto/q_auto on the image/upload path by default', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample' } }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.transformation).toBe('f_auto/q_auto'); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_auto/q_auto/sample`); + expect(out.resource_type).toBe('image'); + expect(out.type).toBe('upload'); + expect(out.public_id).toBe('sample'); + }); + + it('maps a quality level to q_auto:', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample', imageQuality: 'eco' } }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.transformation).toBe('f_auto/q_auto:eco'); + }); +}); + +describe('transform host variants', () => { + it('puts the cloud name in the subdomain for a private CDN', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample' } }); + const [out] = await optimizeImage(ctx, 0, { ...testCreds, privateCdn: true }); + expect(out.secure_url).toBe('https://demo-res.cloudinary.com/image/upload/f_auto/q_auto/sample'); + }); + + it('drops the cloud name for a custom hostname (CNAME)', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample' } }); + const [out] = await optimizeImage(ctx, 0, { + ...testCreds, + secureDistribution: 'assets.example.com', + }); + expect(out.secure_url).toBe('https://assets.example.com/image/upload/f_auto/q_auto/sample'); + }); +}); + +describe('transform delivery type and additional options', () => { + it('threads the top-level delivery type into the URL path', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'sample', type: 'private' }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.type).toBe('private'); + expect(out.secure_url).toBe(`${HOST}/image/private/f_auto/q_auto/sample`); + }); + + it('supports a social/fetch delivery type in the path (e.g. facebook)', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: '65646572251', type: 'facebook' }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.type).toBe('facebook'); + expect(out.secure_url).toBe(`${HOST}/image/facebook/f_auto/q_auto/65646572251`); + }); + + it('threads delivery type and version together', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + type: 'authenticated', + transformAdditionalOptions: { version: '1234' }, + }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.type).toBe('authenticated'); + expect(out.version).toBe('1234'); + expect(out.secure_url).toBe(`${HOST}/image/authenticated/f_auto/q_auto/v1234/sample`); + }); +}); + +describe('transform:resizeImage', () => { + it('emits c_,w_,h_ with both dimensions', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'sample', resizeWidth: 400, resizeHeight: 300, resizeFit: 'fit' }, + }); + const [out] = await resizeImage(ctx, 0, testCreds); + expect(out.transformation).toBe('c_fit,w_400,h_300'); + }); + + it('omits the missing dimension and defaults fit to limit', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample', resizeWidth: 800 } }); + const [out] = await resizeImage(ctx, 0, testCreds); + expect(out.transformation).toBe('c_limit,w_800'); + }); + + it('throws when neither width nor height is given', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample' } }); + await expect(resizeImage(ctx, 0, testCreds)).rejects.toThrow(/width and\/or a height/); + }); +}); + +describe('transform:cropImage', () => { + it('emits c_fill,g_,w_,h_ for dimensions', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'sample', cropWidth: 400, cropHeight: 300, cropFocus: 'face' }, + }); + const [out] = await cropImage(ctx, 0, testCreds); + expect(out.transformation).toBe('c_fill,g_face,w_400,h_300'); + }); + + it('emits ar_,c_fill,g_auto for aspect ratio', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + cropBy: 'aspectRatio', + cropAspectRatio: '16:9', + cropAspectWidth: 800, + }, + }); + const [out] = await cropImage(ctx, 0, testCreds); + expect(out.transformation).toBe('c_fill,g_auto,ar_16:9,w_800'); + }); + + it('switches to b_gen_fill,c_pad when generative fill is on', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + cropBy: 'aspectRatio', + cropAspectRatio: '1:1', + cropGenerativeFill: true, + }, + }); + const [out] = await cropImage(ctx, 0, testCreds); + expect(out.transformation).toBe('b_gen_fill,c_pad,ar_1:1'); + }); + + it('URL-encodes a generative fill prompt', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + cropWidth: 800, + cropHeight: 600, + cropGenerativeFill: true, + cropGenerativeFillPrompt: 'a sandy beach', + }, + }); + const [out] = await cropImage(ctx, 0, testCreds); + expect(out.transformation).toBe('b_gen_fill:prompt_a%20sandy%20beach,c_pad,w_800,h_600'); + }); + + it('throws when aspect ratio mode has no ratio', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'sample', cropBy: 'aspectRatio' }, + }); + await expect(cropImage(ctx, 0, testCreds)).rejects.toThrow(/aspect ratio/); + }); + + it('throws when dimensions mode has no dimensions', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample' } }); + await expect(cropImage(ctx, 0, testCreds)).rejects.toThrow(/width and\/or a height/); + }); +}); + +describe('transform:convertImage', () => { + it('emits f_ and delivers with the format extension', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'sample', convertFormat: 'png' } }); + const [out] = await convertImage(ctx, 0, testCreds); + expect(out.transformation).toBe('f_png'); + expect(out.format).toBe('png'); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_png/sample.png`); + }); +}); + +describe('transform:optimizeVideo', () => { + it('emits f_auto:video/q_auto on the video path', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'clip' } }); + const [out] = await optimizeVideo(ctx, 0, testCreds); + expect(out.transformation).toBe('f_auto:video/q_auto'); + expect(out.resource_type).toBe('video'); + expect(out.secure_url).toBe(`${HOST}/video/upload/f_auto:video/q_auto/clip`); + }); +}); + +describe('transform:trimVideo', () => { + it('joins any combination of so/eo/du with commas', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', trimStart: '2.5', trimDuration: '5' }, + }); + const [out] = await trimVideo(ctx, 0, testCreds); + expect(out.transformation).toBe('so_2.5,du_5'); + }); + + it('supports start + end', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', trimStart: '0', trimEnd: '10' }, + }); + const [out] = await trimVideo(ctx, 0, testCreds); + expect(out.transformation).toBe('so_0,eo_10'); + }); + + it('throws when no bound is given', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'clip' } }); + await expect(trimVideo(ctx, 0, testCreds)).rejects.toThrow(/start, end, or duration/); + }); +}); + +describe('transform:videoThumbnail', () => { + it('uses so_auto and a jpg extension by default', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'clip' } }); + const [out] = await videoThumbnail(ctx, 0, testCreds); + expect(out.transformation).toBe('so_auto'); + expect(out.resource_type).toBe('video'); + expect(out.format).toBe('jpg'); + expect(out.secure_url).toBe(`${HOST}/video/upload/so_auto/clip.jpg`); + }); + + it('uses a specific timestamp and chosen format', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'clip', + thumbnailFrameMode: 'time', + thumbnailTimestamp: '3', + thumbnailFormat: 'png', + }, + }); + const [out] = await videoThumbnail(ctx, 0, testCreds); + expect(out.transformation).toBe('so_3'); + expect(out.secure_url).toBe(`${HOST}/video/upload/so_3/clip.png`); + }); + + it('throws when time mode has no timestamp', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', thumbnailFrameMode: 'time' }, + }); + await expect(videoThumbnail(ctx, 0, testCreds)).rejects.toThrow(/timestamp is required/); + }); + + it('prepends a base transformation before so_auto', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', thumbnailBaseTransformation: 'c_fill,w_800,h_600' }, + }); + const [out] = await videoThumbnail(ctx, 0, testCreds); + expect(out.transformation).toBe('c_fill,w_800,h_600/so_auto'); + expect(out.secure_url).toBe(`${HOST}/video/upload/c_fill,w_800,h_600/so_auto/clip.jpg`); + }); + + it('prepends a base transformation before a specific timestamp', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'clip', + thumbnailBaseTransformation: 'so_5,eo_10', + thumbnailFrameMode: 'time', + thumbnailTimestamp: '2', + }, + }); + const [out] = await videoThumbnail(ctx, 0, testCreds); + expect(out.transformation).toBe('so_5,eo_10/so_2'); + }); + + it('omits base transformation from the URL when blank', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', thumbnailBaseTransformation: '' }, + }); + const [out] = await videoThumbnail(ctx, 0, testCreds); + expect(out.transformation).toBe('so_auto'); + }); +}); + +describe('trailingMediaFormat', () => { + it('detects a standard media extension on the final segment', () => { + expect(trailingMediaFormat('my_image1234.png')).toBe('png'); + expect(trailingMediaFormat('clip.mp4')).toBe('mp4'); + expect(trailingMediaFormat('folder/sub/photo.JPG')).toBe('jpg'); + }); + + it('ignores non-media extensions and folder dots', () => { + expect(trailingMediaFormat('report.final')).toBeUndefined(); + expect(trailingMediaFormat('archive.tar')).toBeUndefined(); + expect(trailingMediaFormat('folder.v2/photo')).toBeUndefined(); + expect(trailingMediaFormat('samples/animals/cat')).toBeUndefined(); + expect(trailingMediaFormat('sample')).toBeUndefined(); + }); +}); + +describe('transform public_id with embedded extension (special case)', () => { + it('re-appends the format so a dotted public_id resolves (optimize)', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'my_image1234.png' } }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.transformation).toBe('f_auto/q_auto'); + expect(out.format).toBe('png'); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_auto/q_auto/my_image1234.png.png`); + }); + + it('re-appends the format for resize too', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'my_image1234.png', resizeWidth: 400 }, + }); + const [out] = await resizeImage(ctx, 0, testCreds); + expect(out.secure_url).toBe(`${HOST}/image/upload/c_limit,w_400/my_image1234.png.png`); + }); + + it('keeps an explicit convert format over the public_id extension', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'my_image1234.png', convertFormat: 'webp' }, + }); + const [out] = await convertImage(ctx, 0, testCreds); + expect(out.format).toBe('webp'); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_webp/my_image1234.png.webp`); + }); + + it('leaves a dot-free public_id untouched', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'samples/cat' } }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.format).toBeUndefined(); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_auto/q_auto/samples/cat`); + }); + + it('recovers the extension for private/authenticated stored assets', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'secret.png', type: 'authenticated' }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.format).toBe('png'); + expect(out.secure_url).toBe(`${HOST}/image/authenticated/f_auto/q_auto/secret.png.png`); + }); + + it('does NOT append an extension to a fetch remote URL', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'https://example.com/photo.jpg', type: 'fetch' }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.format).toBeUndefined(); + expect(out.secure_url).toBe( + `${HOST}/image/fetch/f_auto/q_auto/https://example.com/photo.jpg`, + ); + }); + + it('does NOT append an extension to a social-source id with a dotted segment', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'cloudinary.gif', type: 'twitter_name' }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.format).toBeUndefined(); + expect(out.secure_url).toBe(`${HOST}/image/twitter_name/f_auto/q_auto/cloudinary.gif`); + }); + + it('escapes a fetch URL query string and fragment through the full flow', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'https://example.com/photo.jpg?sig=abc#v1', + type: 'fetch', + }, + }); + const [out] = await optimizeImage(ctx, 0, testCreds); + expect(out.secure_url).toBe( + `${HOST}/image/fetch/f_auto/q_auto/https://example.com/photo.jpg%3Fsig%3Dabc%23v1`, + ); + }); + + it('carries a Convert format via the transformation, not a URL suffix, on a fetch URL', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'https://example.com/photo.jpg', + type: 'fetch', + convertFormat: 'webp', + }, + }); + const [out] = await convertImage(ctx, 0, testCreds); + // f_webp carries the conversion; the remote URL stays the untouched source id, + // and no format key is reported since nothing is appended to the URL. + expect(out.format).toBeUndefined(); + expect(out.secure_url).toBe(`${HOST}/image/fetch/f_webp/https://example.com/photo.jpg`); + }); +}); + +describe('transform:customTransformation', () => { + it('uses the raw transformation string verbatim with the chosen resource type', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'clip', + customResourceType: 'video', + customTransformationString: 'so_0,du_10/f_auto:video/q_auto', + customFormat: 'mp4', + }, + }); + const [out] = await customTransformation(ctx, 0, testCreds); + expect(out.transformation).toBe('so_0,du_10/f_auto:video/q_auto'); + expect(out.resource_type).toBe('video'); + expect(out.format).toBe('mp4'); + expect(out.secure_url).toBe(`${HOST}/video/upload/so_0,du_10/f_auto:video/q_auto/clip.mp4`); + }); + + it('throws on an empty transformation string', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'clip', customTransformationString: ' ' }, + }); + await expect(customTransformation(ctx, 0, testCreds)).rejects.toThrow(/transformation string/); + }); + + it('does not include a format key when none is given', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'sample', customTransformationString: 'e_grayscale' }, + }); + const [out] = await customTransformation(ctx, 0, testCreds); + expect(out.format).toBeUndefined(); + expect((out as IDataObject).secure_url).toBe(`${HOST}/image/upload/e_grayscale/sample`); + }); +}); + +describe('transform:multiStep', () => { + it('chains trim → crop (aspect ratio) → optimize into one video URL in order', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'reel', + multiStepResourceType: 'video', + transformSteps: { + step: [ + { stepType: 'trim', end: '15' }, + { stepType: 'crop', cropMode: 'aspectRatio', aspectRatio: '9:16', focus: 'auto' }, + { stepType: 'optimize', quality: 'auto' }, + ], + }, + }, + }); + const [out] = await multiStep(ctx, 0, testCreds); + expect(out.transformation).toBe('eo_15/c_fill,g_auto,ar_9:16/f_auto:video/q_auto'); + expect(out.resource_type).toBe('video'); + expect(out.secure_url).toBe(`${HOST}/video/upload/eo_15/c_fill,g_auto,ar_9:16/f_auto:video/q_auto/reel`); + }); + + it('emits f_auto (not :video) for the image resource type', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + multiStepResourceType: 'image', + transformSteps: { step: [{ stepType: 'optimize', quality: 'eco' }] }, + }, + }); + const [out] = await multiStep(ctx, 0, testCreds); + expect(out.transformation).toBe('f_auto/q_auto:eco'); + expect(out.secure_url).toBe(`${HOST}/image/upload/f_auto/q_auto:eco/sample`); + }); + + it('resize + convert chains the component and sets the delivery format', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + multiStepResourceType: 'image', + transformSteps: { + step: [ + { stepType: 'resize', width: 800, fit: 'limit' }, + { stepType: 'convert', format: 'webp' }, + ], + }, + }, + }); + const [out] = await multiStep(ctx, 0, testCreds); + expect(out.transformation).toBe('c_limit,w_800/f_webp'); + expect(out.format).toBe('webp'); + expect(out.secure_url).toBe(`${HOST}/image/upload/c_limit,w_800/f_webp/sample.webp`); + }); + + it('crop by dimensions emits c_fill,g_,w_,h_', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + multiStepResourceType: 'image', + transformSteps: { + step: [{ stepType: 'crop', cropMode: 'dimensions', cropWidth: 400, cropHeight: 300, focus: 'face' }], + }, + }, + }); + const [out] = await multiStep(ctx, 0, testCreds); + expect(out.transformation).toBe('c_fill,g_face,w_400,h_300'); + }); + + it('passes a raw component through verbatim', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'sample', + multiStepResourceType: 'image', + transformSteps: { step: [{ stepType: 'raw', raw: 'e_grayscale' }] }, + }, + }); + const [out] = await multiStep(ctx, 0, testCreds); + expect(out.transformation).toBe('e_grayscale'); + }); + + it('throws when no steps are provided', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'reel', multiStepResourceType: 'video' }, + }); + await expect(multiStep(ctx, 0, testCreds)).rejects.toThrow(/at least one transformation step/); + }); + + it('reports the offending step index when a step is incomplete', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'reel', + multiStepResourceType: 'video', + transformSteps: { + step: [{ stepType: 'optimize', quality: 'auto' }, { stepType: 'trim' }], + }, + }, + }); + await expect(multiStep(ctx, 0, testCreds)).rejects.toThrow(/Step 2: Trim requires/); + }); +}); diff --git a/nodes/Cloudinary/operations/transform/trimVideo.ts b/nodes/Cloudinary/operations/transform/trimVideo.ts new file mode 100644 index 0000000..80d0e7b --- /dev/null +++ b/nodes/Cloudinary/operations/transform/trimVideo.ts @@ -0,0 +1,26 @@ +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildComponents, buildTransformResult, readTransformInput, trimComponents } from './shared'; + +/** + * Trim a video. Any combination of start (`so_`), end (`eo_`), and duration (`du_`) + * is allowed; at least one is required. Values are in seconds (e.g. 2.5). + */ +export const trimVideo: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const start = ctx.getNodeParameter('trimStart', i, '') as string; + const end = ctx.getNodeParameter('trimEnd', i, '') as string; + const duration = ctx.getNodeParameter('trimDuration', i, '') as string; + + const components = buildComponents(ctx, i, () => trimComponents({ start, end, duration })); + + return [ + buildTransformResult(creds, { + resourceType: 'video', + type: deliveryType, + transformation: joinTransformation(components), + publicId, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/transform/videoThumbnail.ts b/nodes/Cloudinary/operations/transform/videoThumbnail.ts new file mode 100644 index 0000000..2f52d6b --- /dev/null +++ b/nodes/Cloudinary/operations/transform/videoThumbnail.ts @@ -0,0 +1,44 @@ +import { NodeOperationError } from 'n8n-workflow'; +import { joinTransformation } from '../../cloudinary.utils'; +import { OperationHandler } from '../types'; +import { buildTransformResult, readTransformInput } from './shared'; + +/** + * Generate a still thumbnail from a video: a video public ID delivered with an + * image extension (e.g. `.jpg`), grabbing either an auto-selected frame (`so_auto`) + * or a specific timestamp (`so_`). An optional base transformation is prepended + * before the frame selector so callers can chain from a previous transform step + * (e.g. pipe `{{ $json.transformation }}` from a Trim or Multi-Step node). + */ +export const videoThumbnail: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType, version } = readTransformInput(ctx, i); + const frameMode = ctx.getNodeParameter('thumbnailFrameMode', i, 'auto') as string; + const format = ctx.getNodeParameter('thumbnailFormat', i, 'jpg') as string; + const baseTransformation = (ctx.getNodeParameter('thumbnailBaseTransformation', i, '') as string).trim(); + + let frameComponent: string; + if (frameMode === 'auto') { + frameComponent = 'so_auto'; + } else { + const timestamp = (ctx.getNodeParameter('thumbnailTimestamp', i, '') as string).trim(); + if (!timestamp) { + throw new NodeOperationError( + ctx.getNode(), + 'A timestamp is required when the frame is set to a specific time', + { itemIndex: i }, + ); + } + frameComponent = `so_${timestamp}`; + } + + return [ + buildTransformResult(creds, { + resourceType: 'video', + type: deliveryType, + transformation: joinTransformation([baseTransformation || undefined, frameComponent]), + publicId, + format, + version, + }), + ]; +}; diff --git a/nodes/Cloudinary/operations/types.ts b/nodes/Cloudinary/operations/types.ts new file mode 100644 index 0000000..a778265 --- /dev/null +++ b/nodes/Cloudinary/operations/types.ts @@ -0,0 +1,25 @@ +import { IDataObject, IExecuteFunctions } from 'n8n-workflow'; + +export const CREDENTIAL_TYPE = 'cloudinaryApi'; + +export interface CloudinaryCredentials { + cloudName: string; + apiKey: string; + apiSecret: string; + /** Account delivers from a private CDN (-res.cloudinary.com). Optional. */ + privateCdn?: boolean; + /** Custom delivery hostname (CNAME). Optional. Overrides the default host. */ + secureDistribution?: string; +} + +/** + * A single resource+operation handler. Receives the execution context, the + * current item index, and the resolved credentials; returns zero or more JSON + * objects. The dispatch loop in execute() wraps each into an INodeExecutionData + * with the correct pairedItem, so handlers stay free of n8n output plumbing. + */ +export type OperationHandler = ( + ctx: IExecuteFunctions, + i: number, + creds: CloudinaryCredentials, +) => Promise; diff --git a/nodes/Cloudinary/operations/updateAsset/deleteAssets.ts b/nodes/Cloudinary/operations/updateAsset/deleteAssets.ts new file mode 100644 index 0000000..d0dca57 --- /dev/null +++ b/nodes/Cloudinary/operations/updateAsset/deleteAssets.ts @@ -0,0 +1,53 @@ +import { IDataObject, IHttpRequestOptions, NodeOperationError } from 'n8n-workflow'; +import { + basicAuth, + buildResourceDeleteUrl, + jsonHeaders, + splitCsvIds, +} from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const deleteAssets: OperationHandler = async (ctx, i, creds) => { + const resourceType = ctx.getNodeParameter('resourceType', i) as string; + const type = ctx.getNodeParameter('type', i) as string; + // Accept a single string, a CSV string, or an array (e.g. from an + // n8n expression like `{{ $items().map(i => i.json.public_id) }}`). + const rawPublicIds = ctx.getNodeParameter('publicIds', i) as string | string[]; + const deleteOptions = ctx.getNodeParameter('deleteOptions', i, {}) as IDataObject; + + const publicIds = Array.isArray(rawPublicIds) + ? rawPublicIds.map((s) => String(s).trim()).filter((s) => s.length > 0) + : splitCsvIds(rawPublicIds); + + if (publicIds.length === 0) { + throw new NodeOperationError(ctx.getNode(), 'No public IDs provided', { + description: 'Provide at least one public_id (comma-separated for many).', + itemIndex: i, + }); + } + + // Cloudinary accepts `public_ids` as a comma-separated string or as + // `public_ids[]=...` repeated keys. n8n's default `qs` serializer turns a + // JS array into `public_ids[0]=...` (bracketed indices), which Cloudinary + // rejects. Pre-joining to a CSV side-steps the serializer entirely and + // matches the documented input shape. + const qs: IDataObject = { + public_ids: publicIds.join(','), + ...deleteOptions, + }; + + const options: IHttpRequestOptions = { + method: 'DELETE', + url: buildResourceDeleteUrl(creds.cloudName, resourceType, type), + qs, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/updateAsset/getAsset.ts b/nodes/Cloudinary/operations/updateAsset/getAsset.ts new file mode 100644 index 0000000..ea6866c --- /dev/null +++ b/nodes/Cloudinary/operations/updateAsset/getAsset.ts @@ -0,0 +1,23 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, buildResourceByAssetIdUrl, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const getAsset: OperationHandler = async (ctx, i, creds) => { + const assetId = ctx.getNodeParameter('assetId', i) as string; + const getOptions = ctx.getNodeParameter('getOptions', i, {}) as IDataObject; + + const options: IHttpRequestOptions = { + method: 'GET', + url: buildResourceByAssetIdUrl(creds.cloudName, assetId), + qs: getOptions, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/updateAsset/updateAsset.test.ts b/nodes/Cloudinary/operations/updateAsset/updateAsset.test.ts new file mode 100644 index 0000000..b8334e1 --- /dev/null +++ b/nodes/Cloudinary/operations/updateAsset/updateAsset.test.ts @@ -0,0 +1,204 @@ +import { describe, it, expect } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { deleteAssets } from './deleteAssets'; +import { getAsset } from './getAsset'; +import { updateTags } from './updateTags'; +import { updateMetadata } from './updateMetadata'; +import { makeCtx, lastRequest, testCreds } from '../testHelpers'; + +const resourceParams = { publicId: 'sample', resourceType: 'image', type: 'upload' }; +const PUBLIC_ID_URL = 'https://api.cloudinary.com/v1_1/demo/resources/image/upload/sample'; +const ASSET_ID_URL = 'https://api.cloudinary.com/v1_1/demo/resources/abc123'; +const BULK_DELETE_URL = 'https://api.cloudinary.com/v1_1/demo/resources/image/upload'; + +describe('getAsset handler', () => { + it('GETs the asset_id URL with HTTP Basic auth', async () => { + const { ctx, http } = makeCtx({ params: { assetId: 'abc123', getOptions: {} } }); + + await getAsset(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('GET'); + expect(req.url).toBe(ASSET_ID_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + expect(req.body).toBeUndefined(); + }); + + it('passes getOptions through as query string', async () => { + const { ctx, http } = makeCtx({ + params: { + assetId: 'abc123', + getOptions: { colors: true, faces: true, image_metadata: true }, + }, + }); + + await getAsset(ctx, 0, testCreds); + + expect(lastRequest(http).qs).toEqual({ colors: true, faces: true, image_metadata: true }); + }); +}); + +describe('deleteAssets handler', () => { + const deleteResource = { resourceType: 'image', type: 'upload' }; + + it('DELETEs the per-(resource_type, type) URL with public_ids as CSV in qs and Basic auth', async () => { + const { ctx, http } = makeCtx({ + params: { ...deleteResource, publicIds: 'docs/strawberry', deleteOptions: {} }, + }); + + await deleteAssets(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('DELETE'); + expect(req.url).toBe(BULK_DELETE_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + // Cloudinary wants a comma-separated string, not a JS array — `qs` would + // otherwise serialize an array as public_ids[0]=... which is rejected. + expect((req.qs as IDataObject).public_ids).toBe('docs/strawberry'); + }); + + it('sends a single public_id as a plain string (no array, no brackets)', async () => { + const { ctx, http } = makeCtx({ + params: { ...deleteResource, publicIds: 'solo', deleteOptions: {} }, + }); + + await deleteAssets(ctx, 0, testCreds); + + expect((lastRequest(http).qs as IDataObject).public_ids).toBe('solo'); + }); + + it('splits CSV public_ids and re-joins as a clean CSV (trimmed, blanks dropped)', async () => { + const { ctx, http } = makeCtx({ + params: { + ...deleteResource, + publicIds: ' docs/a ,, docs/b,docs/c ', + deleteOptions: {}, + }, + }); + + await deleteAssets(ctx, 0, testCreds); + + expect((lastRequest(http).qs as IDataObject).public_ids).toBe('docs/a,docs/b,docs/c'); + }); + + it('accepts an array from an n8n expression and serializes it as CSV', async () => { + const { ctx, http } = makeCtx({ + params: { + ...deleteResource, + publicIds: ['docs/a', ' docs/b ', '', 'docs/c'], + deleteOptions: {}, + }, + }); + + await deleteAssets(ctx, 0, testCreds); + + expect((lastRequest(http).qs as IDataObject).public_ids).toBe('docs/a,docs/b,docs/c'); + }); + + it('routes to the correct URL for non-image resource types', async () => { + const { ctx, http } = makeCtx({ + params: { resourceType: 'video', type: 'private', publicIds: 'clip1', deleteOptions: {} }, + }); + + await deleteAssets(ctx, 0, testCreds); + + expect(lastRequest(http).url).toBe( + 'https://api.cloudinary.com/v1_1/demo/resources/video/private', + ); + }); + + it('merges deleteOptions into qs only when set', async () => { + const { ctx, http } = makeCtx({ + params: { + ...deleteResource, + publicIds: 'docs/a', + deleteOptions: { invalidate: true, keep_original: false, next_cursor: 'cur1' }, + }, + }); + + await deleteAssets(ctx, 0, testCreds); + + expect(lastRequest(http).qs).toEqual({ + public_ids: 'docs/a', + invalidate: true, + keep_original: false, + next_cursor: 'cur1', + }); + }); + + it('throws when no public IDs are provided (whitespace only)', async () => { + const { ctx } = makeCtx({ + params: { ...deleteResource, publicIds: ' , ,', deleteOptions: {} }, + }); + + await expect(deleteAssets(ctx, 0, testCreds)).rejects.toThrow('No public IDs provided'); + }); + + it('throws when expression resolves to an empty array', async () => { + const { ctx } = makeCtx({ + params: { ...deleteResource, publicIds: [], deleteOptions: {} }, + }); + + await expect(deleteAssets(ctx, 0, testCreds)).rejects.toThrow('No public IDs provided'); + }); +}); + +describe('updateTags handler', () => { + it('POSTs to the Admin resource-update URL with HTTP Basic auth (no signature)', async () => { + const { ctx, http } = makeCtx({ + params: { ...resourceParams, tags: 'cat,dog', updateOptions: {} }, + }); + + await updateTags(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('POST'); + expect(req.url).toBe(PUBLIC_ID_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + expect((req.body as IDataObject).signature).toBeUndefined(); + }); + + it('sends tags and merges updateOptions into the body', async () => { + const { ctx, http } = makeCtx({ + params: { ...resourceParams, tags: 'cat,dog', updateOptions: { context: 'alt=hi' } }, + }); + + await updateTags(ctx, 0, testCreds); + + expect(lastRequest(http).body).toEqual({ tags: 'cat,dog', context: 'alt=hi' }); + }); +}); + +describe('updateMetadata handler', () => { + it('converts the structured-metadata JSON input to a pipe string', async () => { + const { ctx, http } = makeCtx({ + params: { ...resourceParams, structuredMetadata: '{"color":"red"}', updateOptions: {} }, + }); + + await updateMetadata(ctx, 0, testCreds); + + expect((lastRequest(http).body as IDataObject).metadata).toBe('color=red'); + }); + + it('uses HTTP Basic auth and the resource-update URL', async () => { + const { ctx, http } = makeCtx({ + params: { ...resourceParams, structuredMetadata: '{"color":"red"}', updateOptions: {} }, + }); + + await updateMetadata(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.url).toBe(PUBLIC_ID_URL); + expect(req.auth).toEqual({ username: testCreds.apiKey, password: testCreds.apiSecret }); + }); + + it('propagates the metadata parse error for invalid JSON', async () => { + const { ctx } = makeCtx({ + params: { ...resourceParams, structuredMetadata: '{bad}', updateOptions: {} }, + }); + + await expect(updateMetadata(ctx, 0, testCreds)).rejects.toThrow( + 'Invalid JSON for structured metadata', + ); + }); +}); diff --git a/nodes/Cloudinary/operations/updateAsset/updateMetadata.ts b/nodes/Cloudinary/operations/updateAsset/updateMetadata.ts new file mode 100644 index 0000000..e52147f --- /dev/null +++ b/nodes/Cloudinary/operations/updateAsset/updateMetadata.ts @@ -0,0 +1,31 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, buildResourceUpdateUrl, jsonHeaders, metadataToPipeString } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const updateMetadata: OperationHandler = async (ctx, i, creds) => { + const publicId = ctx.getNodeParameter('publicId', i) as string; + const resourceType = ctx.getNodeParameter('resourceType', i) as string; + const type = ctx.getNodeParameter('type', i) as string; + const structuredMetadata = ctx.getNodeParameter('structuredMetadata', i) as string; + const updateOptions = ctx.getNodeParameter('updateOptions', i, {}) as IDataObject; + + const body: IDataObject = { + metadata: metadataToPipeString(structuredMetadata), + ...updateOptions, + }; + + const options: IHttpRequestOptions = { + method: 'POST', + url: buildResourceUpdateUrl(creds.cloudName, resourceType, type, publicId), + body, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/updateAsset/updateTags.ts b/nodes/Cloudinary/operations/updateAsset/updateTags.ts new file mode 100644 index 0000000..db46222 --- /dev/null +++ b/nodes/Cloudinary/operations/updateAsset/updateTags.ts @@ -0,0 +1,37 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { basicAuth, buildResourceUpdateUrl, jsonHeaders } from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; +import { appendTags } from '../tagAppend'; + +export const updateTags: OperationHandler = async (ctx, i, creds) => { + const tagMode = ctx.getNodeParameter('tagMode', i, 'set') as string; + if (tagMode === 'append') { + return appendTags(ctx, i, creds); + } + + const publicId = ctx.getNodeParameter('publicId', i) as string; + const resourceType = ctx.getNodeParameter('resourceType', i) as string; + const type = ctx.getNodeParameter('type', i) as string; + const tags = ctx.getNodeParameter('tags', i) as string; + const updateOptions = ctx.getNodeParameter('updateOptions', i, {}) as IDataObject; + + const body: IDataObject = { + tags, + ...updateOptions, + }; + + const options: IHttpRequestOptions = { + method: 'POST', + url: buildResourceUpdateUrl(creds.cloudName, resourceType, type, publicId), + body, + headers: jsonHeaders(), + auth: basicAuth(creds), + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/upload/uploadFile.test.ts b/nodes/Cloudinary/operations/upload/uploadFile.test.ts new file mode 100644 index 0000000..969fd82 --- /dev/null +++ b/nodes/Cloudinary/operations/upload/uploadFile.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { uploadFile } from './uploadFile'; +import { generateCloudinarySignature } from '../../cloudinary.utils'; +import { makeCtx, lastRequest, testCreds } from '../testHelpers'; + +const baseParams = { file: 'data', resource_type_file: 'image', additionalFieldsFile: {} }; + +describe('uploadFile handler', () => { + beforeEach(() => { + // Pin the clock so the timestamp (and thus the signature) is deterministic. + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-01-01T00:00:00Z')); + }); + + it('POSTs to the signed-upload endpoint for the chosen resource type', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadFile(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('POST'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/image/upload'); + expect(req.headers?.['Content-Type']).toMatch(/^multipart\/form-data; boundary=/); + }); + + it('includes api_key, timestamp, and a valid signature in the multipart body', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadFile(ctx, 0, testCreds); + + const body = (lastRequest(http).body as Buffer).toString('latin1'); + const timestamp = Math.round(new Date('2026-01-01T00:00:00Z').getTime() / 1000); + const expectedSig = generateCloudinarySignature( + { timestamp, api_key: testCreds.apiKey }, + testCreds.apiSecret, + ); + + expect(body).toContain('name="api_key"'); + expect(body).toContain(testCreds.apiKey); + expect(body).toContain('name="timestamp"'); + expect(body).toContain(String(timestamp)); + expect(body).toContain('name="signature"'); + expect(body).toContain(expectedSig); + }); + + it('does NOT use HTTP Basic auth (signed-upload flow, not Admin API)', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadFile(ctx, 0, testCreds); + + expect(lastRequest(http).auth).toBeUndefined(); + }); + + it('converts structured metadata to a pipe string before signing and sending', async () => { + const { ctx, http } = makeCtx({ + params: { + ...baseParams, + additionalFieldsFile: { metadata: { color: 'red', tags: ['a', 'b'] } }, + }, + }); + + await uploadFile(ctx, 0, testCreds); + + const body = (lastRequest(http).body as Buffer).toString('latin1'); + expect(body).toContain('color=red|tags=["a","b"]'); + }); + + it('sends the binary file part with its filename and mime type', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadFile(ctx, 0, testCreds); + + const body = (lastRequest(http).body as Buffer).toString('latin1'); + expect(body).toContain('filename="cat.png"'); + expect(body).toContain('Content-Type: image/png'); + expect(body).toContain('PNGDATA'); + }); + + it('falls back to a default filename and mime type when binary metadata is missing', async () => { + const { ctx, http } = makeCtx({ params: baseParams, binary: {} }); + + await uploadFile(ctx, 0, testCreds); + + const body = (lastRequest(http).body as Buffer).toString('latin1'); + expect(body).toContain('filename="file"'); + expect(body).toContain('Content-Type: application/octet-stream'); + }); + + it('returns the HTTP response wrapped in an array', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + const response = { public_id: 'sample', secure_url: 'https://res.cloudinary.com/x' }; + http.mockResolvedValue(response); + + const result = await uploadFile(ctx, 0, testCreds); + + expect(result).toEqual([response]); + }); +}); diff --git a/nodes/Cloudinary/operations/upload/uploadFile.ts b/nodes/Cloudinary/operations/upload/uploadFile.ts new file mode 100644 index 0000000..959b3bc --- /dev/null +++ b/nodes/Cloudinary/operations/upload/uploadFile.ts @@ -0,0 +1,65 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { + generateCloudinarySignature, + createMultipartBody, + buildUploadUrl, + metadataToPipeString, + USER_AGENT, +} from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const uploadFile: OperationHandler = async (ctx, i, creds) => { + const binaryPropertyName = ctx.getNodeParameter('file', i) as string; + const resourceType = ctx.getNodeParameter('resource_type_file', i) as string; + const additionalFields = ctx.getNodeParameter('additionalFieldsFile', i, {}) as IDataObject; + + if (additionalFields.metadata) { + additionalFields.metadata = metadataToPipeString( + additionalFields.metadata as IDataObject | string, + ); + } + + const binaryData = ctx.helpers.assertBinaryData(i, binaryPropertyName); + const dataBuffer = await ctx.helpers.getBinaryDataBuffer(i, binaryPropertyName); + + const timestamp = Math.round(new Date().getTime() / 1000); + const params: IDataObject = { + timestamp, + api_key: creds.apiKey, + ...additionalFields, + }; + const signature = generateCloudinarySignature(params, creds.apiSecret); + + const fields: Record = { + api_key: creds.apiKey, + timestamp: timestamp.toString(), + signature, + }; + for (const key in additionalFields) { + fields[key] = additionalFields[key] as string; + } + + const { body, boundary } = createMultipartBody( + fields, + dataBuffer, + binaryData.fileName || 'file', + binaryData.mimeType || 'application/octet-stream', + ); + + const options: IHttpRequestOptions = { + method: 'POST', + url: buildUploadUrl(creds.cloudName, resourceType), + body, + headers: { + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'User-Agent': USER_AGENT, + }, + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/upload/uploadUrl.test.ts b/nodes/Cloudinary/operations/upload/uploadUrl.test.ts new file mode 100644 index 0000000..9170db7 --- /dev/null +++ b/nodes/Cloudinary/operations/upload/uploadUrl.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { IDataObject } from 'n8n-workflow'; +import { uploadUrl } from './uploadUrl'; +import { generateCloudinarySignature } from '../../cloudinary.utils'; +import { makeCtx, lastRequest, testCreds } from '../testHelpers'; + +const baseParams = { + url: 'https://example.com/a.jpg', + resource_type: 'image', + additionalFields: {}, +}; + +describe('uploadUrl handler', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-01-01T00:00:00Z')); + }); + + it('POSTs urlencoded to the signed-upload endpoint', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadUrl(ctx, 0, testCreds); + + const req = lastRequest(http); + expect(req.method).toBe('POST'); + expect(req.url).toBe('https://api.cloudinary.com/v1_1/demo/image/upload'); + expect(req.headers?.['Content-Type']).toBe('application/x-www-form-urlencoded'); + expect(req.auth).toBeUndefined(); + }); + + it('puts the remote url in the body as `file` and signs it', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + + await uploadUrl(ctx, 0, testCreds); + + const body = lastRequest(http).body as IDataObject; + expect(body.file).toBe('https://example.com/a.jpg'); + // `file` is part of the signed payload here (unlike file upload), but + // generateCloudinarySignature excludes it — so the signature must match + // the payload computed without file/api_key/signature. + const timestamp = body.timestamp as number; + expect(body.signature).toBe( + generateCloudinarySignature( + { timestamp, api_key: testCreds.apiKey, file: body.file }, + testCreds.apiSecret, + ), + ); + }); + + it('converts structured metadata to a pipe string in the body', async () => { + const { ctx, http } = makeCtx({ + params: { ...baseParams, additionalFields: { metadata: { a: '1', b: ['x'] } } }, + }); + + await uploadUrl(ctx, 0, testCreds); + + const body = lastRequest(http).body as IDataObject; + expect(body.metadata).toBe('a=1|b=["x"]'); + }); + + it('returns the HTTP response wrapped in an array', async () => { + const { ctx, http } = makeCtx({ params: baseParams }); + http.mockResolvedValue({ public_id: 'sample' }); + + const result = await uploadUrl(ctx, 0, testCreds); + + expect(result).toEqual([{ public_id: 'sample' }]); + }); +}); diff --git a/nodes/Cloudinary/operations/upload/uploadUrl.ts b/nodes/Cloudinary/operations/upload/uploadUrl.ts new file mode 100644 index 0000000..94431e7 --- /dev/null +++ b/nodes/Cloudinary/operations/upload/uploadUrl.ts @@ -0,0 +1,46 @@ +import { IDataObject, IHttpRequestOptions } from 'n8n-workflow'; +import { + generateCloudinarySignature, + buildUploadUrl, + metadataToPipeString, + USER_AGENT, +} from '../../cloudinary.utils'; +import { CREDENTIAL_TYPE, OperationHandler } from '../types'; + +export const uploadUrl: OperationHandler = async (ctx, i, creds) => { + const url = ctx.getNodeParameter('url', i) as string; + const resourceType = ctx.getNodeParameter('resource_type', i) as string; + const additionalFields = ctx.getNodeParameter('additionalFields', i, {}) as IDataObject; + + if (additionalFields.metadata) { + additionalFields.metadata = metadataToPipeString( + additionalFields.metadata as IDataObject | string, + ); + } + + const timestamp = Math.round(new Date().getTime() / 1000); + const params: IDataObject = { + timestamp, + api_key: creds.apiKey, + file: url, + ...additionalFields, + }; + params.signature = generateCloudinarySignature(params, creds.apiSecret); + + const options: IHttpRequestOptions = { + method: 'POST', + url: buildUploadUrl(creds.cloudName, resourceType), + body: params, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + }, + }; + + const response = await ctx.helpers.httpRequestWithAuthentication.call( + ctx, + CREDENTIAL_TYPE, + options, + ); + return [response as IDataObject]; +}; diff --git a/nodes/Cloudinary/operations/widget/videoPlayer.ts b/nodes/Cloudinary/operations/widget/videoPlayer.ts new file mode 100644 index 0000000..46193c4 --- /dev/null +++ b/nodes/Cloudinary/operations/widget/videoPlayer.ts @@ -0,0 +1,146 @@ +import { IDataObject } from 'n8n-workflow'; +import { OperationHandler } from '../types'; +import { readTransformInput } from '../transform/shared'; + +interface PlayerParams { + publicId: string; + deliveryType: string; + autoplayMode: string; + loop: boolean; + muted: boolean; + sourceTypes: string[]; + poster: string; + transformation: string; + fluid: boolean; + width: number; + height: number; + aspectRatio: string; + skin: string; + baseColor: string; + accentColor: string; + textColor: string; + fontFace: string; + advanced: IDataObject; +} + +export const videoPlayer: OperationHandler = async (ctx, i, creds) => { + const { publicId, deliveryType } = readTransformInput(ctx, i); + const p: PlayerParams = { + publicId, + deliveryType, + autoplayMode: ctx.getNodeParameter('playerAutoplayMode', i, '') as string, + loop: ctx.getNodeParameter('playerLoop', i, false) as boolean, + muted: ctx.getNodeParameter('playerMuted', i, false) as boolean, + sourceTypes: ctx.getNodeParameter('playerSourceTypes', i, []) as string[], + poster: ctx.getNodeParameter('playerPoster', i, '') as string, + transformation: ctx.getNodeParameter('playerTransformation', i, '') as string, + fluid: ctx.getNodeParameter('playerFluid', i, false) as boolean, + width: ctx.getNodeParameter('playerWidth', i, 0) as number, + height: ctx.getNodeParameter('playerHeight', i, 0) as number, + aspectRatio: ctx.getNodeParameter('playerAspectRatio', i, '') as string, + skin: ctx.getNodeParameter('playerSkin', i, 'dark') as string, + baseColor: ctx.getNodeParameter('playerBaseColor', i, '') as string, + accentColor: ctx.getNodeParameter('playerAccentColor', i, '') as string, + textColor: ctx.getNodeParameter('playerTextColor', i, '') as string, + fontFace: ctx.getNodeParameter('playerFontFace', i, '') as string, + advanced: ctx.getNodeParameter('playerAdvancedOptions', i, {}) as IDataObject, + }; + + const embedUrl = buildEmbedUrl(creds.cloudName, p); + + return [ + { + embed_url: embedUrl, + player_config: JSON.stringify(buildPlayerConfig(creds.cloudName, p), null, 2), + public_id: publicId, + resource_type: 'video', + type: deliveryType, + }, + ]; +}; + +// player[...] keys use camelCase, matching the embed-page examples in the Video +// Player docs. The embedder accepts snake_case equivalents too (verified in-browser: +// player[aspectRatio] and player[aspect_ratio] behave identically), so don't "fix" +// these to snake_case — both work and camelCase mirrors the documented examples. +function buildEmbedUrl(cloudName: string, p: PlayerParams): string { + const q: string[] = [ + `cloud_name=${enc(cloudName)}`, + `public_id=${enc(p.publicId)}`, + ]; + + if (p.autoplayMode) q.push(`player[autoplayMode]=${enc(p.autoplayMode)}`); + if (p.loop) q.push('player[loop]=true'); + if (p.muted) q.push('player[muted]=true'); + if (p.fluid) q.push('player[fluid]=true'); + if (p.width) q.push(`player[width]=${p.width}`); + if (p.height) q.push(`player[height]=${p.height}`); + if (p.aspectRatio) q.push(`player[aspectRatio]=${enc(p.aspectRatio)}`); + if (p.skin && p.skin !== 'dark') q.push(`player[skin]=${enc(p.skin)}`); + if (p.baseColor) q.push(`player[colors][base]=${enc(p.baseColor)}`); + if (p.accentColor) q.push(`player[colors][accent]=${enc(p.accentColor)}`); + if (p.textColor) q.push(`player[colors][text]=${enc(p.textColor)}`); + if (p.fontFace) q.push(`player[fontFace]=${enc(p.fontFace)}`); + + p.sourceTypes.forEach((st, idx) => q.push(`source[sourceTypes][${idx}]=${enc(st)}`)); + + if (p.poster) q.push(`source[poster]=${enc(p.poster)}`); + + const adv = p.advanced; + if (adv.controls === false) q.push('player[controls]=false'); + if (adv.playsinline) q.push('player[playsinline]=true'); + if (adv.bigPlayButton === false) q.push('player[bigPlayButton]=false'); + if (adv.pictureInPictureToggle) q.push('player[pictureInPictureToggle]=true'); + if (adv.chaptersButton) q.push('player[chaptersButton]=true'); + if (adv.seekThumbnails === false) q.push('player[seekThumbnails]=false'); + if (adv.hdr) q.push('player[hdr]=true'); + if (adv.floatingWhenNotVisible && adv.floatingWhenNotVisible !== 'disabled') { + q.push(`player[floatingWhenNotVisible]=${enc(String(adv.floatingWhenNotVisible))}`); + } + + // source[transformation] intentionally omitted: in the embed URL it applies to the + // poster image only, not the video stream, so it would be misleading here. + + return `https://player.cloudinary.com/embed/?${q.join('&')}`; +} + +function buildPlayerConfig(cloudName: string, p: PlayerParams): IDataObject { + const cfg: IDataObject = { cloudName, publicId: p.publicId }; + + if (p.deliveryType && p.deliveryType !== 'upload') cfg.type = p.deliveryType; + if (p.autoplayMode) cfg.autoplayMode = p.autoplayMode; + if (p.loop) cfg.loop = true; + if (p.muted) cfg.muted = true; + if (p.fluid) cfg.fluid = true; + if (p.width) cfg.width = p.width; + if (p.height) cfg.height = p.height; + if (p.aspectRatio) cfg.aspectRatio = p.aspectRatio; + if (p.skin && p.skin !== 'dark') cfg.skin = p.skin; + + const colors: IDataObject = {}; + if (p.baseColor) colors.base = p.baseColor; + if (p.accentColor) colors.accent = p.accentColor; + if (p.textColor) colors.text = p.textColor; + if (Object.keys(colors).length) cfg.colors = colors; + + if (p.fontFace) cfg.fontFace = p.fontFace; + if (p.sourceTypes.length) cfg.sourceTypes = p.sourceTypes; + if (p.poster) cfg.poster = p.poster; + if (p.transformation) cfg.transformation = [{ raw_transformation: p.transformation }]; + + const adv = p.advanced; + if (adv.controls === false) cfg.controls = false; + if (adv.playsinline) cfg.playsinline = true; + if (adv.bigPlayButton === false) cfg.bigPlayButton = false; + if (adv.pictureInPictureToggle) cfg.pictureInPictureToggle = true; + if (adv.chaptersButton) cfg.chaptersButton = true; + if (adv.seekThumbnails === false) cfg.seekThumbnails = false; + if (adv.hdr) cfg.hdr = true; + if (adv.floatingWhenNotVisible && adv.floatingWhenNotVisible !== 'disabled') { + cfg.floatingWhenNotVisible = adv.floatingWhenNotVisible; + } + + return cfg; +} + +const enc = (v: string) => encodeURIComponent(v); diff --git a/nodes/Cloudinary/operations/widget/widget.test.ts b/nodes/Cloudinary/operations/widget/widget.test.ts new file mode 100644 index 0000000..50e64ca --- /dev/null +++ b/nodes/Cloudinary/operations/widget/widget.test.ts @@ -0,0 +1,155 @@ +import { describe, it, expect } from 'vitest'; +import { videoPlayer } from './videoPlayer'; +import { makeCtx, testCreds } from '../testHelpers'; + +// videoPlayer builds an iframe embed URL + a self-hosted player_config JSON and makes +// NO HTTP call, so tests assert the returned JSON, not a request spy. makeCtx supplies +// the only surface the handler touches (getNodeParameter + getNode). + +const HOST = 'https://res.cloudinary.com/demo'; + +describe('widget:videoPlayer', () => { + it('returns embed_url, player_config, and identity fields', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'my-video' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('cloud_name=demo'); + expect(out.embed_url).toContain('public_id=my-video'); + expect(typeof out.player_config).toBe('string'); + expect(out.public_id).toBe('my-video'); + expect(out.resource_type).toBe('video'); + expect(out.type).toBe('upload'); + }); + + it('includes autoplayMode in embed_url when set', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerAutoplayMode: 'always' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[autoplayMode]=always'); + }); + + it('omits autoplayMode from embed_url when blank', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerAutoplayMode: '' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).not.toContain('player[autoplayMode]'); + }); + + it('includes muted and loop flags in embed_url', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerMuted: true, playerLoop: true } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[muted]=true'); + expect(out.embed_url).toContain('player[loop]=true'); + }); + + it('includes source types as indexed source[sourceTypes] params', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerSourceTypes: ['hls', 'mp4'] } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('source[sourceTypes][0]=hls'); + expect(out.embed_url).toContain('source[sourceTypes][1]=mp4'); + }); + + it('includes colors in embed_url, URL-encoded', async () => { + const { ctx } = makeCtx({ + params: { + transformPublicId: 'v', + playerBaseColor: '#0071ba', + playerAccentColor: '#db8226', + playerTextColor: '#ffffff', + }, + }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[colors][base]=%230071ba'); + expect(out.embed_url).toContain('player[colors][accent]=%23db8226'); + expect(out.embed_url).toContain('player[colors][text]=%23ffffff'); + }); + + it('omits transformation from embed_url', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerTransformation: 'q_auto,f_auto' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).not.toContain('transformation'); + }); + + it('includes transformation in player_config as raw_transformation array', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerTransformation: 'q_auto,f_auto' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.transformation).toEqual([{ raw_transformation: 'q_auto,f_auto' }]); + }); + + it('includes player[skin]=light for light skin, omits for default dark', async () => { + const { ctx: ctxLight } = makeCtx({ params: { transformPublicId: 'v', playerSkin: 'light' } }); + const [outLight] = await videoPlayer(ctxLight, 0, testCreds); + expect(outLight.embed_url).toContain('player[skin]=light'); + + const { ctx: ctxDark } = makeCtx({ params: { transformPublicId: 'v', playerSkin: 'dark' } }); + const [outDark] = await videoPlayer(ctxDark, 0, testCreds); + expect(outDark.embed_url).not.toContain('player[skin]'); + }); + + it('includes player[fluid]=true when playerFluid is true', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerFluid: true } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[fluid]=true'); + }); + + it('includes aspect ratio in embed_url, URL-encoded', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerFluid: true, playerAspectRatio: '9:16' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[aspectRatio]=9%3A16'); + }); + + it('includes width and height in embed_url', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerWidth: 800, playerHeight: 450 } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[width]=800'); + expect(out.embed_url).toContain('player[height]=450'); + }); + + it('player_config is valid JSON with cloudName and publicId', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'my-video' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.cloudName).toBe('demo'); + expect(cfg.publicId).toBe('my-video'); + }); + + it('player_config includes colors object for set color fields', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'v', playerBaseColor: '#0071ba', playerAccentColor: '#db8226' }, + }); + const [out] = await videoPlayer(ctx, 0, testCreds); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.colors).toEqual({ base: '#0071ba', accent: '#db8226' }); + }); + + it('includes advanced controls=false in embed_url and player_config', async () => { + const { ctx } = makeCtx({ + params: { transformPublicId: 'v', playerAdvancedOptions: { controls: false } }, + }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain('player[controls]=false'); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.controls).toBe(false); + }); + + it('includes non-upload delivery type in player_config but not embed_url', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', type: 'authenticated' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.type).toBe('authenticated'); + expect(out.embed_url).not.toContain('authenticated'); + }); + + it('includes poster in embed_url as source[poster] and in player_config', async () => { + const posterUrl = `${HOST}/video/upload/so_auto/clip.jpg`; + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerPoster: posterUrl } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).toContain(`source[poster]=${encodeURIComponent(posterUrl)}`); + const cfg = JSON.parse(out.player_config as string); + expect(cfg.poster).toBe(posterUrl); + }); + + it('omits source[poster] from embed_url when poster is blank', async () => { + const { ctx } = makeCtx({ params: { transformPublicId: 'v', playerPoster: '' } }); + const [out] = await videoPlayer(ctx, 0, testCreds); + expect(out.embed_url).not.toContain('source[poster]'); + }); +}); diff --git a/package-lock.json b/package-lock.json index 62bc8b0..8aed8e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-nodes-cloudinary", - "version": "0.0.8", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-nodes-cloudinary", - "version": "0.0.8", + "version": "0.1.0", "license": "MIT", "devDependencies": { "@types/node": "^24.1.0", @@ -16,7 +16,8 @@ "gulp": "^5.0.0", "prettier": "^3.5.3", "semantic-release": "^24.2.0", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "vitest": "^4.1.7" }, "engines": { "node": ">=20.19" @@ -26,13 +27,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "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.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -41,9 +42,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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": { @@ -61,10 +62,44 @@ "node": ">=0.1.90" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -82,7 +117,7 @@ }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "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", @@ -94,9 +129,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "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": { @@ -105,7 +140,7 @@ }, "node_modules/@eslint/eslintrc": { "version": "2.1.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", @@ -128,9 +163,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -139,9 +174,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -153,7 +188,7 @@ }, "node_modules/@eslint/js": { "version": "8.57.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@eslint/js/-/js-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", @@ -163,7 +198,7 @@ }, "node_modules/@gulpjs/messages": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@gulpjs/messages/-/messages-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", "dev": true, "license": "MIT", @@ -173,7 +208,7 @@ }, "node_modules/@gulpjs/to-absolute-glob": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", "dev": true, "license": "MIT", @@ -186,8 +221,9 @@ }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -200,9 +236,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -211,9 +247,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -225,7 +261,7 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", @@ -239,24 +275,60 @@ }, "node_modules/@humanwhocodes/object-schema": { "version": "2.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@n8n_io/riot-tmpl": { - "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.0.tgz", - "integrity": "sha512-/xw8HQgYQlBCrt3IKpNSSB1CgpP7XArw1QTRjP+KEw+OHT8XGvHxXrW9VGdUu9RwDnzm/LFu+dNLeDmwJMeOwQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.1.tgz", + "integrity": "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==", "license": "MIT", "peer": true, "dependencies": { "eslint-config-riot": "^1.0.0" } }, + "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==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "callsites": "3.1.0" + } + }, + "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==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "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/tournament": { "version": "1.0.6", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@n8n/tournament/-/tournament-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.0.6.tgz", "integrity": "sha512-UGSxYXXVuOX0yL6HTLBStKYwLIa0+JmRKiSZSCMcM2s2Wax984KWT6XIA1TR/27i7yYpDk1MY14KsTPnuEp27A==", "license": "Apache-2.0", "peer": true, @@ -271,62 +343,28 @@ "pnpm": ">=9.5" } }, - "node_modules/@n8n/tournament/node_modules/@n8n_io/riot-tmpl": { - "version": "4.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.1.tgz", - "integrity": "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "eslint-config-riot": "^1.0.0" - } - }, - "node_modules/@n8n/tournament/node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@n8n/tournament/node_modules/recast": { - "version": "0.22.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/recast/-/recast-0.22.0.tgz", - "integrity": "sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "assert": "^2.0.0", - "ast-types": "0.15.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@n8n/tournament/node_modules/recast/node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", @@ -340,7 +378,7 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", @@ -350,7 +388,7 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", @@ -373,17 +411,17 @@ } }, "node_modules/@octokit/core": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.5.tgz", - "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "dependencies": { "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.2", - "@octokit/request": "^10.0.4", - "@octokit/request-error": "^7.0.1", - "@octokit/types": "^15.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, @@ -392,13 +430,13 @@ } }, "node_modules/@octokit/endpoint": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.1.tgz", - "integrity": "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^15.0.0", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" }, "engines": { @@ -406,14 +444,14 @@ } }, "node_modules/@octokit/graphql": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.2.tgz", - "integrity": "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^10.0.4", - "@octokit/types": "^15.0.0", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" }, "engines": { @@ -421,20 +459,20 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", - "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.0.tgz", - "integrity": "sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^15.0.0" + "@octokit/types": "^15.0.1" }, "engines": { "node": ">= 20" @@ -443,15 +481,32 @@ "@octokit/core": ">=6" } }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^26.0.0" + } + }, "node_modules/@octokit/plugin-retry": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.2.tgz", - "integrity": "sha512-mVPCe77iaD8g1lIX46n9bHPUirFLzc3BfIzsZOpB7bcQh1ecS63YsAgcsyMGqvGa2ARQWKEFTrhMJX2MLJVHVw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request-error": "^7.0.1", - "@octokit/types": "^15.0.0", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -462,13 +517,13 @@ } }, "node_modules/@octokit/plugin-throttling": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.2.tgz", - "integrity": "sha512-ntNIig4zZhQVOZF4fG9Wt8QCoz9ehb+xnlUwp74Ic2ANChCk8oKmRwV9zDDCtrvU1aERIOvtng8wsalEX7Jk5Q==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^15.0.0", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -479,16 +534,17 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.5.tgz", - "integrity": "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.10.tgz", + "integrity": "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^11.0.1", - "@octokit/request-error": "^7.0.1", - "@octokit/types": "^15.0.0", - "fast-content-type-parse": "^3.0.0", + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "content-type": "^2.0.0", + "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" }, "engines": { @@ -496,26 +552,36 @@ } }, "node_modules/@octokit/request-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.1.tgz", - "integrity": "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^15.0.0" + "@octokit/types": "^16.0.0" }, "engines": { "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.0.tgz", - "integrity": "sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^26.0.0" + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@pnpm/config.env-replace": { @@ -549,9 +615,9 @@ "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -563,141 +629,403 @@ "node": ">=12" } }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", - "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "import-from-esm": "^2.0.0", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@semantic-release/github": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", - "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@octokit/core": "^7.0.0", - "@octokit/plugin-paginate-rest": "^13.0.0", - "@octokit/plugin-retry": "^8.0.0", - "@octokit/plugin-throttling": "^11.0.0", - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "debug": "^4.3.4", - "dir-glob": "^3.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "issue-parser": "^7.0.0", - "lodash-es": "^4.17.21", - "mime": "^4.0.0", - "p-filter": "^4.0.0", - "tinyglobby": "^0.2.14", - "url-join": "^5.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=24.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@semantic-release/npm": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", - "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "execa": "^9.0.0", - "fs-extra": "^11.0.0", - "lodash-es": "^4.17.21", - "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.9.3", - "rc": "^1.2.8", - "read-pkg": "^9.0.0", - "registry-auth-token": "^5.0.0", - "semver": "^7.1.2", - "tempy": "^3.0.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", - "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^2.0.0", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-package-up": "^11.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", + "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=24.1.0" + } + }, + "node_modules/@semantic-release/npm": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.9.3", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.1.tgz", + "integrity": "sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" } }, "node_modules/@sindresorhus/is": { @@ -726,21 +1054,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "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/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/normalize-package-data": { @@ -750,16 +1114,9 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/parser": { "version": "8.32.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", @@ -782,9 +1139,45 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.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/project-service/node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "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/scope-manager": { "version": "8.32.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "dev": true, "license": "MIT", @@ -800,9 +1193,26 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "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/types": { "version": "8.32.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/types/-/types-8.32.1.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", @@ -816,7 +1226,7 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.32.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "dev": true, "license": "MIT", @@ -842,43 +1252,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -886,13 +1294,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -900,97 +1308,106 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.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": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/utils/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/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.32.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", @@ -1007,16 +1424,129 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "dev": true, "license": "ISC" }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -1028,7 +1558,7 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", @@ -1064,9 +1594,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1081,9 +1611,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", - "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", + "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": { @@ -1097,17 +1627,21 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ansi-styles/-/ansi-styles-4.3.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { @@ -1129,7 +1663,7 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/anymatch/-/anymatch-3.1.3.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", @@ -1143,7 +1677,7 @@ }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/argparse/-/argparse-2.0.1.tgz", + "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" @@ -1157,7 +1691,7 @@ }, "node_modules/array-each": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/array-each/-/array-each-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", "dev": true, "license": "MIT", @@ -1174,7 +1708,7 @@ }, "node_modules/array-slice": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/array-slice/-/array-slice-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true, "license": "MIT", @@ -1182,19 +1716,9 @@ "node": ">=0.10.0" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/assert": { "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/assert/-/assert-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "license": "MIT", "peer": true, @@ -1206,10 +1730,20 @@ "util": "^0.12.5" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "license": "MIT", "peer": true, "dependencies": { @@ -1221,7 +1755,7 @@ }, "node_modules/async-done": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/async-done/-/async-done-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", "dev": true, "license": "MIT", @@ -1236,7 +1770,7 @@ }, "node_modules/async-settle": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/async-settle/-/async-settle-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", "dev": true, "license": "MIT", @@ -1249,14 +1783,14 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/asynckit/-/asynckit-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT", "peer": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "license": "MIT", "peer": true, @@ -1270,28 +1804,24 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "license": "MIT", - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } }, "node_modules/bach": { "version": "2.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/bach/-/bach-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", "dev": true, "license": "MIT", @@ -1306,22 +1836,29 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/balanced-match/-/balanced-match-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/bare-events": { - "version": "2.6.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/bare-events/-/bare-events-2.6.0.tgz", - "integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", "dev": true, "license": "Apache-2.0", - "optional": true + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/base64-js/-/base64-js-1.5.1.tgz", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ @@ -1349,7 +1886,7 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/binary-extensions/-/binary-extensions-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", @@ -1362,7 +1899,7 @@ }, "node_modules/bl": { "version": "5.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/bl/-/bl-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", "dev": true, "license": "MIT", @@ -1372,6 +1909,21 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/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/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -1380,9 +1932,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -1391,7 +1943,7 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/braces/-/braces-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", @@ -1404,7 +1956,7 @@ }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/buffer/-/buffer-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ @@ -1428,15 +1980,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "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", "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "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": { @@ -1448,7 +2000,7 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "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", "peer": true, @@ -1462,7 +2014,7 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/call-bound/-/call-bound-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "peer": true, @@ -1479,7 +2031,7 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/callsites/-/callsites-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", "engines": { @@ -1488,7 +2040,7 @@ }, "node_modules/camel-case": { "version": "4.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/camel-case/-/camel-case-4.1.2.tgz", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, "license": "MIT", @@ -1497,9 +2049,19 @@ "tslib": "^2.0.3" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/chalk/-/chalk-4.1.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", @@ -1526,7 +2088,7 @@ }, "node_modules/charenc": { "version": "0.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/charenc/-/charenc-0.0.2.tgz", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "license": "BSD-3-Clause", "peer": true, @@ -1536,7 +2098,7 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/chokidar/-/chokidar-3.6.0.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", @@ -1561,7 +2123,7 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob-parent/-/glob-parent-5.1.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", @@ -1641,7 +2203,7 @@ }, "node_modules/cliui": { "version": "7.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/cliui/-/cliui-7.0.4.tgz", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "license": "ISC", @@ -1653,7 +2215,7 @@ }, "node_modules/clone": { "version": "2.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/clone/-/clone-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "dev": true, "license": "MIT", @@ -1663,7 +2225,7 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/color-convert/-/color-convert-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { @@ -1675,13 +2237,13 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/color-name/-/color-name-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/combined-stream/-/combined-stream-1.0.8.tgz", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "peer": true, @@ -1705,7 +2267,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/concat-map/-/concat-map-0.0.1.tgz", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" @@ -1721,10 +2283,24 @@ "proto-list": "~1.2.1" } }, + "node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz", + "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==", "dev": true, "license": "ISC", "dependencies": { @@ -1735,12 +2311,13 @@ } }, "node_modules/conventional-changelog-writer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", - "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz", + "integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==", "dev": true, "license": "MIT", "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", "meow": "^13.0.0", @@ -1764,12 +2341,13 @@ } }, "node_modules/conventional-commits-parser": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.0.tgz", - "integrity": "sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", "dev": true, "license": "MIT", "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", "meow": "^13.0.0" }, "bin": { @@ -1794,14 +2372,14 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/convert-source-map/-/convert-source-map-2.0.0.tgz", + "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/copy-props": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/copy-props/-/copy-props-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", "dev": true, "license": "MIT", @@ -1821,9 +2399,9 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1849,7 +2427,7 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/cross-spawn/-/cross-spawn-7.0.6.tgz", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", @@ -1864,7 +2442,7 @@ }, "node_modules/crypt": { "version": "0.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/crypt/-/crypt-0.0.2.tgz", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "license": "BSD-3-Clause", "peer": true, @@ -1902,9 +2480,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1919,35 +2497,6 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1960,14 +2509,14 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/deep-is/-/deep-is-0.1.4.tgz", + "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/define-data-property": { "version": "1.1.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/define-data-property/-/define-data-property-1.1.4.tgz", + "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", "peer": true, @@ -1985,7 +2534,7 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/define-properties/-/define-properties-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "peer": true, @@ -2003,7 +2552,7 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/delayed-stream/-/delayed-stream-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "peer": true, @@ -2013,7 +2562,7 @@ }, "node_modules/detect-file": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/detect-file/-/detect-file-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true, "license": "MIT", @@ -2021,9 +2570,19 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/dir-glob/-/dir-glob-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", @@ -2036,7 +2595,7 @@ }, "node_modules/doctrine": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/doctrine/-/doctrine-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", @@ -2062,7 +2621,7 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/dunder-proto/-/dunder-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "peer": true, @@ -2085,49 +2644,9 @@ "readable-stream": "^2.0.2" } }, - "node_modules/duplexer2/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/duplexer2/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/duplexer2/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/duplexer2/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/each-props": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/each-props/-/each-props-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", "dev": true, "license": "MIT", @@ -2141,7 +2660,7 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/emoji-regex/-/emoji-regex-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, @@ -2154,7 +2673,7 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/end-of-stream/-/end-of-stream-1.4.5.tgz", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", @@ -2313,7 +2832,7 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/es-define-property/-/es-define-property-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "peer": true, @@ -2323,43 +2842,44 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/es-errors/-/es-errors-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "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", "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "es-errors": "^1.3.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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", "peer": true, "dependencies": { - "es-errors": "^1.3.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -2367,7 +2887,7 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/escalade/-/escalade-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { @@ -2376,7 +2896,7 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", @@ -2389,8 +2909,9 @@ }, "node_modules/eslint": { "version": "8.57.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint/-/eslint-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -2445,29 +2966,21 @@ }, "node_modules/eslint-config-riot": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", "integrity": "sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==", "license": "MIT", "peer": true }, - "node_modules/eslint-plugin-local": { - "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/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": "1.16.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.16.3.tgz", - "integrity": "sha512-edLX42Vg4B+y0kzkitTVDmHZQrG5/wUZO874N5Z9leBuxt5TG1pqMY4zdr35RlpM4p4REr/T9x+6DpsQSL63WA==", + "version": "1.16.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.16.6.tgz", + "integrity": "sha512-heQzTeNmCTxElMLrtRAeWtreaXe9QlKEhZNRQfedtE6k15I0acNyfGvPfXwRfShAWJsAsuJYYHRtEPypeB7kyA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^6.21.0", + "@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", @@ -2481,7 +2994,7 @@ }, "node_modules/eslint-scope": { "version": "7.2.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-scope/-/eslint-scope-7.2.2.tgz", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", @@ -2498,7 +3011,7 @@ }, "node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "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", @@ -2510,9 +3023,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -2522,7 +3035,7 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "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", @@ -2534,9 +3047,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -2548,7 +3061,7 @@ }, "node_modules/espree": { "version": "9.6.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/espree/-/espree-9.6.1.tgz", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", @@ -2566,7 +3079,7 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "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", @@ -2579,7 +3092,7 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/esprima/-/esprima-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "license": "BSD-2-Clause", "peer": true, @@ -2593,7 +3106,7 @@ }, "node_modules/esprima-next": { "version": "5.8.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/esprima-next/-/esprima-next-5.8.4.tgz", + "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", "license": "BSD-2-Clause", "peer": true, @@ -2606,9 +3119,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "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": { @@ -2620,7 +3133,7 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/esrecurse/-/esrecurse-4.3.0.tgz", + "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", @@ -2633,7 +3146,7 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/estraverse/-/estraverse-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", @@ -2641,9 +3154,19 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/esutils/-/esutils-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", @@ -2651,10 +3174,20 @@ "node": ">=0.10.0" } }, + "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": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2697,7 +3230,7 @@ }, "node_modules/expand-tilde": { "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/expand-tilde/-/expand-tilde-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, "license": "MIT", @@ -2708,47 +3241,40 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/extend/-/extend-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, "license": "MIT" }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-fifo/-/fast-fifo-1.3.2.tgz", + "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-glob/-/fast-glob-3.3.3.tgz", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", @@ -2765,7 +3291,7 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob-parent/-/glob-parent-5.1.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", @@ -2778,21 +3304,21 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", @@ -2801,9 +3327,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -2828,7 +3354,7 @@ }, "node_modules/file-entry-cache": { "version": "6.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", @@ -2841,7 +3367,7 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fill-range/-/fill-range-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", @@ -2854,7 +3380,7 @@ }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/find-up/-/find-up-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", @@ -2901,7 +3427,7 @@ }, "node_modules/findup-sync": { "version": "5.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/findup-sync/-/findup-sync-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", "dev": true, "license": "MIT", @@ -2917,7 +3443,7 @@ }, "node_modules/fined": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fined/-/fined-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", "dev": true, "license": "MIT", @@ -2934,7 +3460,7 @@ }, "node_modules/flagged-respawn": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", "dev": true, "license": "MIT", @@ -2944,7 +3470,7 @@ }, "node_modules/flat-cache": { "version": "3.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/flat-cache/-/flat-cache-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", @@ -2958,36 +3484,15 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/for-each/-/for-each-0.3.5.tgz", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", "peer": true, @@ -3003,97 +3508,48 @@ }, "node_modules/for-in": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/for-in/-/for-in-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "node": ">=0.10.0" } }, - "node_modules/from2/node_modules/isarray": { + "node_modules/for-own": { "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/from2/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==", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", "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" + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/from2/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/from2/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, + "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==", "license": "MIT", + "peer": true, "dependencies": { - "safe-buffer": "~5.1.0" + "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/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3107,7 +3563,7 @@ }, "node_modules/fs-mkdirp-stream": { "version": "2.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", "dev": true, "license": "MIT", @@ -3121,14 +3577,14 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fs.realpath/-/fs.realpath-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fsevents/-/fsevents-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, @@ -3143,7 +3599,7 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/function-bind/-/function-bind-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { @@ -3163,19 +3619,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "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==", "license": "MIT", "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/get-caller-file/-/get-caller-file-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { @@ -3184,7 +3640,7 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "peer": true, @@ -3209,7 +3665,7 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/get-proto/-/get-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "peer": true, @@ -3251,8 +3707,9 @@ }, "node_modules/glob": { "version": "7.2.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob/-/glob-7.2.3.tgz", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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": { @@ -3272,7 +3729,7 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob-parent/-/glob-parent-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", @@ -3285,7 +3742,7 @@ }, "node_modules/glob-stream": { "version": "8.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob-stream/-/glob-stream-8.0.3.tgz", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", "dev": true, "license": "MIT", @@ -3305,7 +3762,7 @@ }, "node_modules/glob-watcher": { "version": "6.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glob-watcher/-/glob-watcher-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", "dev": true, "license": "MIT", @@ -3318,9 +3775,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3329,9 +3786,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -3343,7 +3800,7 @@ }, "node_modules/global-modules": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/global-modules/-/global-modules-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "license": "MIT", @@ -3358,7 +3815,7 @@ }, "node_modules/global-prefix": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/global-prefix/-/global-prefix-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, "license": "MIT", @@ -3375,7 +3832,7 @@ }, "node_modules/global-prefix/node_modules/which": { "version": "1.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/which/-/which-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "license": "ISC", @@ -3388,7 +3845,7 @@ }, "node_modules/globals": { "version": "13.24.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/globals/-/globals-13.24.0.tgz", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", @@ -3402,30 +3859,9 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glogg": { "version": "2.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/glogg/-/glogg-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", "dev": true, "license": "MIT", @@ -3438,7 +3874,7 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/gopd/-/gopd-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "peer": true, @@ -3451,21 +3887,21 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/graceful-fs/-/graceful-fs-4.2.11.tgz", + "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/graphemer": { "version": "1.4.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/graphemer/-/graphemer-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, "node_modules/gulp": { "version": "5.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/gulp/-/gulp-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.1.tgz", "integrity": "sha512-PErok3DZSA5WGMd6XXV3IRNO0mlB+wW3OzhFJLEec1jSERg2j1bxJ6e5Fh6N6fn3FH2T9AP4UYNb/pYlADB9sA==", "dev": true, "license": "MIT", @@ -3484,7 +3920,7 @@ }, "node_modules/gulp-cli": { "version": "3.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/gulp-cli/-/gulp-cli-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.1.0.tgz", "integrity": "sha512-zZzwlmEsTfXcxRKiCHsdyjZZnFvXWM4v1NqBJSYbuApkvVKivjcmOS2qruAJ+PkEHLFavcDKH40DPc1+t12a9Q==", "dev": true, "license": "MIT", @@ -3511,7 +3947,7 @@ }, "node_modules/gulplog": { "version": "2.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/gulplog/-/gulplog-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", "dev": true, "license": "MIT", @@ -3523,9 +3959,9 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3544,22 +3980,9 @@ "uglify-js": "^3.1.4" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/has-flag/-/has-flag-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", @@ -3569,7 +3992,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "peer": true, @@ -3582,7 +4005,7 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/has-symbols/-/has-symbols-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "peer": true, @@ -3595,7 +4018,7 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "peer": true, @@ -3610,9 +4033,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3633,7 +4056,7 @@ }, "node_modules/homedir-polyfill": { "version": "1.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "dev": true, "license": "MIT", @@ -3710,7 +4133,7 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/iconv-lite/-/iconv-lite-0.6.3.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", @@ -3723,7 +4146,7 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ieee754/-/ieee754-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ @@ -3744,7 +4167,7 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ignore/-/ignore-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", @@ -3754,7 +4177,7 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/import-fresh/-/import-fresh-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", @@ -3796,7 +4219,7 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/imurmurhash/-/imurmurhash-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", @@ -3805,9 +4228,9 @@ } }, "node_modules/indefinite": { - "version": "2.5.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/indefinite/-/indefinite-2.5.1.tgz", - "integrity": "sha512-Ul0hCdnSjuFDEloYWeozTaEfljbz+0q+u4HsHns2dOk2DlhGlbRMGFtNcIL+Ve7sZYeIOTOAKA0usAXBGHpNDg==", + "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": { @@ -3842,8 +4265,9 @@ }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/inflight/-/inflight-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -3853,35 +4277,20 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/inherits/-/inherits-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ini/-/ini-1.3.8.tgz", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/interpret": { "version": "3.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/interpret/-/interpret-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "license": "MIT", @@ -3889,26 +4298,9 @@ "node": ">=10.13.0" } }, - "node_modules/into-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-absolute": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-absolute/-/is-absolute-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "license": "MIT", @@ -3922,7 +4314,7 @@ }, "node_modules/is-arguments": { "version": "1.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-arguments/-/is-arguments-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", "peer": true, @@ -3937,24 +4329,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "peer": true, - "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-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3962,25 +4336,9 @@ "dev": true, "license": "MIT" }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", @@ -3991,33 +4349,16 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "peer": true, - "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-buffer/-/is-buffer-1.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "license": "MIT", "peer": true }, "node_modules/is-callable": { "version": "1.2.7", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-callable/-/is-callable-1.2.7.tgz", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", "peer": true, @@ -4029,30 +4370,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -4063,7 +4387,7 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", @@ -4073,7 +4397,7 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { @@ -4081,14 +4405,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "license": "MIT", "peer": true, "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "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" }, @@ -4101,7 +4426,7 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-glob/-/is-glob-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", @@ -4112,22 +4437,9 @@ "node": ">=0.10.0" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-nan": { "version": "1.3.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-nan/-/is-nan-1.3.2.tgz", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "license": "MIT", "peer": true, @@ -4144,7 +4456,7 @@ }, "node_modules/is-negated-glob": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "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", @@ -4154,7 +4466,7 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", @@ -4162,23 +4474,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "peer": true, - "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-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -4191,7 +4486,7 @@ }, "node_modules/is-path-inside": { "version": "3.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-path-inside/-/is-path-inside-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", @@ -4202,109 +4497,37 @@ "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": "5.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "peer": true, - "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "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==", "license": "MIT", "peer": true, "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4313,27 +4536,35 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "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", - "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" + "is-unc-path": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-typed-array": { "version": "1.1.15", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-typed-array/-/is-typed-array-1.1.15.tgz", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "license": "MIT", "peer": true, @@ -4349,7 +4580,7 @@ }, "node_modules/is-unc-path": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-unc-path/-/is-unc-path-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "license": "MIT", @@ -4375,7 +4606,7 @@ }, "node_modules/is-valid-glob": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", "dev": true, "license": "MIT", @@ -4383,39 +4614,9 @@ "node": ">=0.10.0" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "peer": true, - "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/is-windows/-/is-windows-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, "license": "MIT", @@ -4424,22 +4625,22 @@ } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT", - "peer": true + "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/isexe": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/isexe/-/isexe-2.0.0.tgz", + "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/isobject/-/isobject-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "license": "MIT", @@ -4447,10 +4648,24 @@ "node": ">=0.10.0" } }, + "node_modules/isolated-vm": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.1.2.tgz", + "integrity": "sha512-GGfsHqtlZiiurZaxB/3kY7LLAXR3sgzDul0fom4cSyBjx6ZbjpTrFWiH3z/nUfLJGJ8PIq9LQmQFiAxu24+I7A==", + "hasInstallScript": true, + "license": "ISC", + "peer": true, + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/issue-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.2.tgz", + "integrity": "sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==", "dev": true, "license": "MIT", "dependencies": { @@ -4476,7 +4691,7 @@ }, "node_modules/jmespath": { "version": "0.16.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/jmespath/-/jmespath-0.16.0.tgz", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", "license": "Apache-2.0", "peer": true, @@ -4486,7 +4701,7 @@ }, "node_modules/js-base64": { "version": "3.7.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/js-base64/-/js-base64-3.7.2.tgz", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==", "license": "BSD-3-Clause", "peer": true @@ -4499,9 +4714,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -4513,7 +4728,7 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/json-buffer/-/json-buffer-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" @@ -4534,22 +4749,29 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "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/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "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-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "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": { @@ -4559,9 +4781,19 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonrepair": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.2.tgz", + "integrity": "sha512-Leuly0nbM4R+S5SVJk3VHfw1oxnlEK9KygdZvfUtEtTawNDyzB4qa1xWTmFt1aeoA7sXZkVTRuIixJ8bAvqVUg==", + "license": "ISC", + "peer": true, + "bin": { + "jsonrepair": "bin/cli.js" + } + }, "node_modules/jssha": { "version": "3.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/jssha/-/jssha-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", "license": "BSD-3-Clause", "peer": true, @@ -4571,7 +4803,7 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/keyv/-/keyv-4.5.4.tgz", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", @@ -4581,7 +4813,7 @@ }, "node_modules/last-run": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/last-run/-/last-run-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", "dev": true, "license": "MIT", @@ -4591,7 +4823,7 @@ }, "node_modules/lead": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/lead/-/lead-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", "dev": true, "license": "MIT", @@ -4601,7 +4833,7 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/levn/-/levn-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", @@ -4615,7 +4847,7 @@ }, "node_modules/liftoff": { "version": "5.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/liftoff/-/liftoff-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", "dev": true, "license": "MIT", @@ -4632,6 +4864,267 @@ "node": ">=10.13.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4671,7 +5164,7 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/locate-path/-/locate-path-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", @@ -4686,16 +5179,16 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT", "peer": true }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "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" }, @@ -4729,7 +5222,7 @@ }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/lodash.merge/-/lodash.merge-4.6.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" @@ -4743,34 +5236,75 @@ }, "node_modules/lower-case": { "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/lower-case/-/lower-case-2.0.2.tgz", + "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" + "tslib": "^2.0.3" + } + }, + "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/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/make-asynchronous/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": "ISC" - }, - "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "license": "MIT", - "peer": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/map-cache": { "version": "0.2.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/map-cache/-/map-cache-0.2.2.tgz", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true, "license": "MIT", @@ -4813,19 +5347,6 @@ "marked": ">=1 <16" } }, - "node_modules/marked-terminal/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "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", @@ -4841,7 +5362,7 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "peer": true, @@ -4851,7 +5372,7 @@ }, "node_modules/md5": { "version": "2.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/md5/-/md5-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "license": "BSD-3-Clause", "peer": true, @@ -4883,7 +5404,7 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/merge2/-/merge2-1.4.1.tgz", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", @@ -4893,7 +5414,7 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/micromatch/-/micromatch-4.0.8.tgz", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", @@ -4923,7 +5444,7 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/mime-db/-/mime-db-1.52.0.tgz", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "peer": true, @@ -4933,7 +5454,7 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/mime-types/-/mime-types-2.1.35.tgz", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "peer": true, @@ -4958,13 +5479,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "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.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4985,14 +5506,14 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ms/-/ms-2.1.3.tgz", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/mute-stdout": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/mute-stdout/-/mute-stdout-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", "dev": true, "license": "MIT", @@ -5013,36 +5534,56 @@ } }, "node_modules/n8n-workflow": { - "version": "1.82.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/n8n-workflow/-/n8n-workflow-1.82.0.tgz", - "integrity": "sha512-KScpufwmC7NtYhUbjQxfKUKrRq11qQH/yJScM3MsFCj2GCqNFQdlmZAmrWj30DeRlwgEdRzNW1LnGIKMGFEVqA==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.16.0.tgz", + "integrity": "sha512-JoDvHLvV4QZQBCDVQl5xkrGOmPmsacLO4TkJkErCzMmiEqIej+h14J6eCUY7gbxBj8V+GIBlpqyO63akJbXRQg==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@n8n_io/riot-tmpl": "4.0.0", + "@n8n/errors": "0.6.0", + "@n8n/expression-runtime": "0.8.0", "@n8n/tournament": "1.0.6", - "ast-types": "0.15.2", - "axios": "1.8.2", + "ast-types": "0.16.1", "callsites": "3.1.0", - "deep-equal": "2.2.0", "esprima-next": "5.8.4", - "form-data": "4.0.0", + "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.21", - "luxon": "3.4.4", + "lodash": "4.17.23", + "luxon": "3.7.2", "md5": "2.3.0", - "recast": "0.21.5", + "recast": "0.22.0", "title-case": "3.0.3", "transliteration": "2.3.5", + "uuid": "10.0.0", "xml2js": "0.6.2", - "zod": "3.24.1" + "zod": "3.25.67" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/natural-compare": { "version": "1.4.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/natural-compare/-/natural-compare-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" @@ -5063,7 +5604,7 @@ }, "node_modules/no-case": { "version": "3.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/no-case/-/no-case-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, "license": "MIT", @@ -5088,6 +5629,18 @@ "node": ">=18" } }, + "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==", + "license": "MIT", + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -5118,7 +5671,7 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", @@ -5127,9 +5680,9 @@ } }, "node_modules/normalize-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", - "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", "dev": true, "license": "MIT", "engines": { @@ -5141,7 +5694,7 @@ }, "node_modules/now-and-later": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/now-and-later/-/now-and-later-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, "license": "MIT", @@ -5153,9 +5706,9 @@ } }, "node_modules/npm": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", - "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "version": "10.9.8", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.8.tgz", + "integrity": "sha512-fYwb6ODSmHkqrJQQaCxY3M2lPf/mpgC7ik0HSzzIwG5CGtabRp4bNqikatvCoT42b5INQSqudVH0R7yVmC9hVg==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -5237,24 +5790,24 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.1", + "@npmcli/arborist": "^8.0.5", "@npmcli/config": "^9.0.0", "@npmcli/fs": "^4.0.0", "@npmcli/map-workspaces": "^4.0.2", "@npmcli/package-json": "^6.2.0", - "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/promise-spawn": "^8.0.3", "@npmcli/redact": "^3.2.2", "@npmcli/run-script": "^9.1.0", "@sigstore/tuf": "^3.1.1", "abbrev": "^3.0.1", "archy": "~1.0.0", "cacache": "^19.0.1", - "chalk": "^5.4.1", - "ci-info": "^4.2.0", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.4.5", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "hosted-git-info": "^8.1.0", "ini": "^5.0.0", @@ -5262,46 +5815,46 @@ "is-cidr": "^5.1.1", "json-parse-even-better-errors": "^4.0.0", "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.1", - "libnpmexec": "^9.0.1", - "libnpmfund": "^6.0.1", + "libnpmdiff": "^7.0.5", + "libnpmexec": "^9.0.5", + "libnpmfund": "^6.0.5", "libnpmhook": "^11.0.0", "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.1", - "libnpmpublish": "^10.0.1", + "libnpmpack": "^8.0.5", + "libnpmpublish": "^10.0.2", "libnpmsearch": "^8.0.0", "libnpmteam": "^7.0.0", "libnpmversion": "^7.0.0", "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", + "minimatch": "^9.0.9", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^11.2.0", + "node-gyp": "^11.5.0", "nopt": "^8.1.0", - "normalize-package-data": "^7.0.0", + "normalize-package-data": "^7.0.1", "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", + "npm-install-checks": "^7.1.2", "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-profile": "^11.0.1", "npm-registry-fetch": "^18.0.2", "npm-user-validate": "^3.0.0", - "p-map": "^7.0.3", + "p-map": "^7.0.4", "pacote": "^19.0.1", "parse-conflict-json": "^4.0.0", "proc-log": "^5.0.0", "qrcode-terminal": "^0.12.0", "read": "^4.1.0", - "semver": "^7.7.2", + "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^12.0.0", "supports-color": "^9.4.0", - "tar": "^6.2.1", + "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.1", + "validate-npm-package-name": "^6.0.2", "which": "^5.0.0", "write-file-atomic": "^6.0.0" }, @@ -5361,7 +5914,7 @@ } }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", + "version": "6.2.2", "dev": true, "inBundle": true, "license": "MIT", @@ -5396,12 +5949,12 @@ } }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "version": "7.2.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -5445,7 +5998,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.1", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -5480,6 +6033,7 @@ "proggy": "^3.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", + "promise-retry": "^2.0.1", "read-package-json-fast": "^4.0.0", "semver": "^7.3.7", "ssri": "^12.0.0", @@ -5591,7 +6145,7 @@ } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", + "version": "20.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -5612,7 +6166,7 @@ "promise-retry": "^2.0.1", "sigstore": "^3.0.0", "ssri": "^12.0.0", - "tar": "^6.1.11" + "tar": "^7.5.10" }, "bin": { "pacote": "bin/index.js" @@ -5658,7 +6212,7 @@ } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", + "version": "8.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -5717,6 +6271,27 @@ "node": ">=14" } }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { "version": "0.4.3", "dev": true, @@ -5726,6 +6301,23 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm/node_modules/@sigstore/tuf": { "version": "3.1.1", "dev": true, @@ -5739,6 +6331,20 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm/node_modules/@tufjs/canonical-json": { "version": "2.0.0", "dev": true, @@ -5758,7 +6364,7 @@ } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.3", + "version": "7.1.4", "dev": true, "inBundle": true, "license": "MIT", @@ -5776,7 +6382,7 @@ } }, "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", + "version": "6.2.3", "dev": true, "inBundle": true, "license": "MIT", @@ -5788,7 +6394,7 @@ } }, "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC" @@ -5865,58 +6471,8 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/npm/node_modules/chalk": { - "version": "5.4.1", + "version": "5.6.2", "dev": true, "inBundle": true, "license": "MIT", @@ -5928,16 +6484,16 @@ } }, "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.2.0", + "version": "4.4.0", "dev": true, "funding": [ { @@ -6051,7 +6607,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.4.1", + "version": "4.4.3", "dev": true, "inBundle": true, "license": "MIT", @@ -6068,7 +6624,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "5.2.0", + "version": "5.2.2", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -6114,7 +6670,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.2", + "version": "3.1.3", "dev": true, "inBundle": true, "license": "Apache-2.0" @@ -6128,6 +6684,23 @@ "node": ">= 4.9.1" } }, + "node_modules/npm/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/npm/node_modules/foreground-child": { "version": "3.3.1", "dev": true, @@ -6157,7 +6730,7 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", "dev": true, "inBundle": true, "license": "ISC", @@ -6288,14 +6861,10 @@ } }, "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -6354,12 +6923,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "4.0.0", "dev": true, @@ -6413,31 +6976,31 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.1", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.1", + "@npmcli/arborist": "^8.0.5", "@npmcli/installed-package-contents": "^3.0.0", "binary-extensions": "^2.3.0", "diff": "^5.1.0", "minimatch": "^9.0.4", "npm-package-arg": "^12.0.0", "pacote": "^19.0.0", - "tar": "^6.2.1" + "tar": "^7.5.11" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.1", + "version": "9.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.1", + "@npmcli/arborist": "^8.0.5", "@npmcli/run-script": "^9.0.1", "ci-info": "^4.0.0", "npm-package-arg": "^12.0.0", @@ -6453,12 +7016,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.1", + "version": "6.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.1" + "@npmcli/arborist": "^8.0.5" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -6491,12 +7054,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.1", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.1", + "@npmcli/arborist": "^8.0.5", "@npmcli/run-script": "^9.0.1", "npm-package-arg": "^12.0.0", "pacote": "^19.0.0" @@ -6506,7 +7069,7 @@ } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", + "version": "10.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -6593,22 +7156,13 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6618,10 +7172,10 @@ } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6679,6 +7233,12 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -6703,6 +7263,12 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", "dev": true, @@ -6727,8 +7293,14 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minizlib": { - "version": "3.0.2", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -6739,18 +7311,6 @@ "node": ">= 18" } }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", "dev": true, @@ -6761,13 +7321,22 @@ "version": "2.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.6" } }, "node_modules/npm/node_modules/node-gyp": { - "version": "11.2.0", + "version": "11.5.0", "dev": true, "inBundle": true, "license": "MIT", @@ -6790,56 +7359,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/npm/node_modules/nopt": { "version": "8.1.0", "dev": true, @@ -6856,7 +7375,7 @@ } }, "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -6891,7 +7410,7 @@ } }, "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", + "version": "7.1.2", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -6995,7 +7514,7 @@ } }, "node_modules/npm/node_modules/p-map": { - "version": "7.0.3", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "MIT", @@ -7013,7 +7532,7 @@ "license": "BlueOak-1.0.0" }, "node_modules/npm/node_modules/pacote": { - "version": "19.0.1", + "version": "19.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -7034,7 +7553,7 @@ "promise-retry": "^2.0.1", "sigstore": "^3.0.0", "ssri": "^12.0.0", - "tar": "^6.1.11" + "tar": "^7.5.10" }, "bin": { "pacote": "bin/index.js" @@ -7082,8 +7601,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/npm/node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.0", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", @@ -7215,7 +7746,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.4", "dev": true, "inBundle": true, "license": "ISC", @@ -7276,58 +7807,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", "dev": true, @@ -7339,12 +7818,12 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.5", + "version": "2.8.7", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -7403,17 +7882,11 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.21", + "version": "3.0.23", "dev": true, "inBundle": true, "license": "CC0-1.0" }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ssri": { "version": "12.0.0", "dev": true, @@ -7493,78 +7966,19 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "dev": true, - "inBundle": 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/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", + "version": "7.5.11", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/npm/node_modules/text-table": { @@ -7580,13 +7994,13 @@ "license": "MIT" }, "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.14", + "version": "0.2.15", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -7595,32 +8009,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "dev": true, - "inBundle": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", "dev": true, @@ -7631,14 +8019,14 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -7708,7 +8096,7 @@ } }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.1", + "version": "6.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -7738,12 +8126,12 @@ } }, "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "version": "3.1.5", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/npm/node_modules/wrap-ansi": { @@ -7797,7 +8185,7 @@ } }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", + "version": "6.2.2", "dev": true, "inBundle": true, "license": "MIT", @@ -7832,12 +8220,12 @@ } }, "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "version": "7.2.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -7860,10 +8248,13 @@ } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -7875,22 +8266,9 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-is": { "version": "1.1.6", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object-is/-/object-is-1.1.6.tgz", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", "peer": true, @@ -7907,7 +8285,7 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object-keys/-/object-keys-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", "peer": true, @@ -7917,7 +8295,7 @@ }, "node_modules/object.assign": { "version": "4.1.7", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object.assign/-/object.assign-4.1.7.tgz", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "license": "MIT", "peer": true, @@ -7938,7 +8316,7 @@ }, "node_modules/object.defaults": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object.defaults/-/object.defaults-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", "dev": true, "license": "MIT", @@ -7954,7 +8332,7 @@ }, "node_modules/object.pick": { "version": "1.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/object.pick/-/object.pick-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", "dev": true, "license": "MIT", @@ -7965,9 +8343,20 @@ "node": ">=0.10.0" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/once/-/once-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", @@ -7993,7 +8382,7 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/optionator/-/optionator-0.9.4.tgz", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", @@ -8016,7 +8405,23 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8038,19 +8443,9 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/p-limit/-/p-limit-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", @@ -8066,7 +8461,7 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/p-locate/-/p-locate-5.0.0.tgz", + "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", @@ -8081,9 +8476,9 @@ } }, "node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -8106,6 +8501,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -8118,7 +8526,7 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/parent-module/-/parent-module-1.0.1.tgz", + "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", @@ -8131,7 +8539,7 @@ }, "node_modules/parse-filepath": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/parse-filepath/-/parse-filepath-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", "dev": true, "license": "MIT", @@ -8178,7 +8586,7 @@ }, "node_modules/parse-passwd": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/parse-passwd/-/parse-passwd-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", "dev": true, "license": "MIT", @@ -8212,7 +8620,7 @@ }, "node_modules/pascal-case": { "version": "3.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/pascal-case/-/pascal-case-3.1.2.tgz", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, "license": "MIT", @@ -8223,7 +8631,7 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-exists/-/path-exists-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", @@ -8233,7 +8641,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", @@ -8243,7 +8651,7 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-key/-/path-key-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", @@ -8253,14 +8661,14 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-parse/-/path-parse-1.0.7.tgz", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "node_modules/path-root": { "version": "0.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-root/-/path-root-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", "dev": true, "license": "MIT", @@ -8273,7 +8681,7 @@ }, "node_modules/path-root-regex": { "version": "0.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-root-regex/-/path-root-regex-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true, "license": "MIT", @@ -8283,7 +8691,7 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/path-type/-/path-type-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", @@ -8291,6 +8699,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8299,9 +8714,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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": { @@ -8400,7 +8815,7 @@ }, "node_modules/pluralize": { "version": "8.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/pluralize/-/pluralize-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, "license": "MIT", @@ -8410,7 +8825,7 @@ }, "node_modules/possible-typed-array-names": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "license": "MIT", "peer": true, @@ -8418,9 +8833,38 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/prelude-ls/-/prelude-ls-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", @@ -8429,9 +8873,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -8474,16 +8918,9 @@ "dev": true, "license": "ISC" }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "peer": true - }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/punycode/-/punycode-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", @@ -8493,7 +8930,7 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/queue-microtask/-/queue-microtask-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ @@ -8634,23 +9071,24 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "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/readdirp": { "version": "3.6.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/readdirp/-/readdirp-3.6.0.tgz", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", @@ -8662,12 +9100,13 @@ } }, "node_modules/recast": { - "version": "0.21.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/recast/-/recast-0.21.5.tgz", - "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.22.0.tgz", + "integrity": "sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==", "license": "MIT", "peer": true, "dependencies": { + "assert": "^2.0.0", "ast-types": "0.15.2", "esprima": "~4.0.0", "source-map": "~0.6.1", @@ -8677,48 +9116,40 @@ "node": ">= 4" } }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, + "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==", "license": "MIT", + "peer": true, "dependencies": { - "resolve": "^1.20.0" + "tslib": "^2.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=4" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, "license": "MIT", - "peer": true, "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" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.13.0" } }, "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "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": "^2.1.0" + "@pnpm/npm-conf": "^3.0.2" }, "engines": { "node": ">=14" @@ -8726,14 +9157,14 @@ }, "node_modules/remove-trailing-separator": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/replace-ext/-/replace-ext-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, "license": "MIT", @@ -8743,7 +9174,7 @@ }, "node_modules/replace-homedir": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/replace-homedir/-/replace-homedir-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", "dev": true, "license": "MIT", @@ -8753,7 +9184,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/require-directory/-/require-directory-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { @@ -8761,13 +9192,14 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -8783,7 +9215,7 @@ }, "node_modules/resolve-dir": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/resolve-dir/-/resolve-dir-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", "dev": true, "license": "MIT", @@ -8797,7 +9229,7 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", @@ -8807,7 +9239,7 @@ }, "node_modules/resolve-options": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/resolve-options/-/resolve-options-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", "dev": true, "license": "MIT", @@ -8820,7 +9252,7 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/reusify/-/reusify-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", @@ -8831,8 +9263,9 @@ }, "node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/rimraf/-/rimraf-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -8845,9 +9278,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/run-parallel/-/run-parallel-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ @@ -8870,29 +9337,15 @@ } }, "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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, - "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-regex-test": { "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "peer": true, @@ -8910,17 +9363,20 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC", - "peer": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=11.0.0" + } }, "node_modules/semantic-release": { "version": "24.2.9", @@ -9021,9 +9477,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { @@ -9052,7 +9508,7 @@ }, "node_modules/semver-greatest-satisfied-range": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", "dev": true, "license": "MIT", @@ -9078,7 +9534,7 @@ }, "node_modules/sentence-case": { "version": "3.0.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/sentence-case/-/sentence-case-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, "license": "MIT", @@ -9090,7 +9546,7 @@ }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/set-function-length/-/set-function-length-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "license": "MIT", "peer": true, @@ -9106,25 +9562,9 @@ "node": ">= 0.4" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "peer": true, - "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/shebang-command": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/shebang-command/-/shebang-command-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", @@ -9137,7 +9577,7 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/shebang-regex/-/shebang-regex-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", @@ -9145,81 +9585,12 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "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.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "peer": true, - "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://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "peer": true, - "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", @@ -9353,28 +9724,28 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/source-map/-/source-map-0.6.1.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparkles": { "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/sparkles/-/sparkles-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", "dev": true, "license": "MIT", @@ -9409,100 +9780,60 @@ }, "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.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", - "dev": true, - "license": "ISC", - "dependencies": { - "through2": "~2.0.0" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "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": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/stream-combiner2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, - "license": "MIT" + "license": "CC0-1.0" }, - "node_modules/stream-combiner2/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==", + "node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", "dev": true, - "license": "MIT", + "license": "ISC", "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" + "through2": "~2.0.0" } }, - "node_modules/stream-combiner2/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==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, - "node_modules/stream-combiner2/node_modules/string_decoder": { + "node_modules/stream-combiner2": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" } }, "node_modules/stream-composer": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/stream-composer/-/stream-composer-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", "dev": true, "license": "MIT", @@ -9512,38 +9843,36 @@ }, "node_modules/stream-exhaust": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", "dev": true, "license": "MIT" }, "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", "dev": true, "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "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.2.0" + "safe-buffer": "~5.1.0" } }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/string-width/-/string-width-4.2.3.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { @@ -9557,7 +9886,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/strip-ansi/-/strip-ansi-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { @@ -9567,6 +9896,15 @@ "node": ">=8" } }, + "node_modules/strip-ansi/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -9592,7 +9930,7 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "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", @@ -9604,13 +9942,14 @@ } }, "node_modules/super-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", - "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", "dev": true, "license": "MIT", "dependencies": { "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" }, "engines": { @@ -9622,7 +9961,7 @@ }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/supports-color/-/supports-color-7.2.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", @@ -9652,7 +9991,7 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", @@ -9665,7 +10004,7 @@ }, "node_modules/sver": { "version": "1.8.4", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/sver/-/sver-1.8.4.tgz", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", "dev": true, "license": "MIT", @@ -9675,7 +10014,7 @@ }, "node_modules/sver/node_modules/semver": { "version": "6.3.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", @@ -9686,7 +10025,7 @@ }, "node_modules/teex": { "version": "1.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/teex/-/teex-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, "license": "MIT", @@ -9705,9 +10044,9 @@ } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9750,9 +10089,9 @@ } }, "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "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": { @@ -9761,7 +10100,7 @@ }, "node_modules/text-table": { "version": "0.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/text-table/-/text-table-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, "license": "MIT" @@ -9800,46 +10139,6 @@ "xtend": "~4.0.1" } }, - "node_modules/through2/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/through2/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/through2/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/through2/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/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -9856,15 +10155,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -9892,9 +10208,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9904,9 +10220,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/title-case": { "version": "3.0.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/title-case/-/title-case-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", "license": "MIT", "dependencies": { @@ -9915,7 +10241,7 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/to-regex-range/-/to-regex-range-5.0.1.tgz", + "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", @@ -9928,7 +10254,7 @@ }, "node_modules/to-through": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/to-through/-/to-through-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", "dev": true, "license": "MIT", @@ -9941,7 +10267,7 @@ }, "node_modules/transliteration": { "version": "2.3.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/transliteration/-/transliteration-2.3.5.tgz", + "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.3.5.tgz", "integrity": "sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==", "license": "MIT", "peer": true, @@ -9958,7 +10284,7 @@ }, "node_modules/transliteration/node_modules/cliui": { "version": "8.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/cliui/-/cliui-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "peer": true, @@ -9973,7 +10299,7 @@ }, "node_modules/transliteration/node_modules/yargs": { "version": "17.7.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/yargs/-/yargs-17.7.2.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "peer": true, @@ -9992,7 +10318,7 @@ }, "node_modules/transliteration/node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/yargs-parser/-/yargs-parser-21.1.1.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "peer": true, @@ -10014,9 +10340,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "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": { @@ -10028,13 +10354,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/tslib/-/tslib-2.8.1.tgz", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/type-check/-/type-check-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", @@ -10047,7 +10373,7 @@ }, "node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/type-fest/-/type-fest-0.20.2.tgz", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", @@ -10060,7 +10386,7 @@ }, "node_modules/typescript": { "version": "5.8.3", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/typescript/-/typescript-5.8.3.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", @@ -10088,7 +10414,7 @@ }, "node_modules/unc-path-regex": { "version": "0.1.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", "dev": true, "license": "MIT", @@ -10098,7 +10424,7 @@ }, "node_modules/undertaker": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/undertaker/-/undertaker-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, "license": "MIT", @@ -10114,7 +10440,7 @@ }, "node_modules/undertaker-registry": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", "dev": true, "license": "MIT", @@ -10124,7 +10450,7 @@ }, "node_modules/undertaker/node_modules/fast-levenshtein": { "version": "3.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "dev": true, "license": "MIT", @@ -10133,9 +10459,9 @@ } }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -10197,7 +10523,7 @@ }, "node_modules/upper-case-first": { "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/upper-case-first/-/upper-case-first-2.0.2.tgz", + "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", @@ -10207,7 +10533,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/uri-js/-/uri-js-4.4.1.tgz", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", @@ -10227,7 +10553,7 @@ }, "node_modules/util": { "version": "0.12.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/util/-/util-0.12.5.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", "peer": true, @@ -10241,14 +10567,29 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "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).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8flags": { "version": "4.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/v8flags/-/v8flags-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", "dev": true, "license": "MIT", @@ -10269,7 +10610,7 @@ }, "node_modules/value-or-function": { "version": "4.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/value-or-function/-/value-or-function-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", "dev": true, "license": "MIT", @@ -10279,7 +10620,7 @@ }, "node_modules/vinyl": { "version": "3.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/vinyl/-/vinyl-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", "dev": true, "license": "MIT", @@ -10295,7 +10636,7 @@ }, "node_modules/vinyl-contents": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", "dev": true, "license": "MIT", @@ -10309,7 +10650,7 @@ }, "node_modules/vinyl-fs": { "version": "4.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", "dev": true, "license": "MIT", @@ -10335,7 +10676,7 @@ }, "node_modules/vinyl-sourcemap": { "version": "2.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", "dev": true, "license": "MIT", @@ -10351,70 +10692,232 @@ "node": ">=10.13.0" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" }, "bin": { - "node-which": "bin/node-which" + "vite": "bin/vite.js" }, "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", - "peer": true, - "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": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">= 0.4" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", - "peer": true, - "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" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" + }, + "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-typed-array": { - "version": "1.1.19", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", "license": "MIT", "peer": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -10428,9 +10931,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/word-wrap/-/word-wrap-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", @@ -10447,7 +10967,7 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { @@ -10464,14 +10984,14 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/wrappy/-/wrappy-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/xml2js": { "version": "0.6.2", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/xml2js/-/xml2js-0.6.2.tgz", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "license": "MIT", "peer": true, @@ -10485,7 +11005,7 @@ }, "node_modules/xmlbuilder": { "version": "11.0.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "license": "MIT", "peer": true, @@ -10505,7 +11025,7 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/y18n/-/y18n-5.0.8.tgz", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { @@ -10514,7 +11034,7 @@ }, "node_modules/yargs": { "version": "16.2.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/yargs/-/yargs-16.2.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "license": "MIT", @@ -10533,7 +11053,7 @@ }, "node_modules/yargs-parser": { "version": "20.2.9", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/yargs-parser/-/yargs-parser-20.2.9.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", @@ -10543,7 +11063,7 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/yocto-queue/-/yocto-queue-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", @@ -10568,9 +11088,9 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://cloudinary-232482882421.d.codeartifact.us-east-1.amazonaws.com/npm/cld-npm-store/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", "license": "MIT", "peer": true, "funding": { diff --git a/package.json b/package.json index 226f2b9..f573051 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-cloudinary", - "version": "0.0.9", + "version": "0.1.0", "description": "The official Cloudinary n8n node - upload media, update asset tags and metadata, and more", "keywords": [ "n8n-community-node-package", @@ -30,7 +30,10 @@ "lint": "eslint nodes credentials package.json", "lintfix": "eslint nodes credentials package.json --fix", "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json", - "n8n-validate": "npx @n8n/scan-community-package n8n-nodes-cloudinary" + "n8n-validate": "npx @n8n/scan-community-package n8n-nodes-cloudinary", + "test": "vitest run", + "test:watch": "vitest", + "backward-compatibility-check": "bash docs/backward-compatibility-check/run.sh" }, "files": [ "dist" @@ -52,7 +55,8 @@ "gulp": "^5.0.0", "prettier": "^3.5.3", "semantic-release": "^24.2.0", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "vitest": "^4.1.7" }, "peerDependencies": { "n8n-workflow": "*" diff --git a/tsconfig.json b/tsconfig.json index 7469d24..baa1dc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,4 +27,8 @@ "nodes/**/*.json", "package.json", ], + "exclude": [ + "**/*.test.ts", + "**/testHelpers.ts", + ], } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c26f063 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + // n8n-workflow's "import" condition points at raw src/index.ts, which vitest + // can't load. Pin to the built CJS entry so runtime imports (e.g. ApplicationError) + // resolve during tests. + 'n8n-workflow': 'n8n-workflow/dist/cjs/index.js', + }, + }, +});