From a151238e20cd70541883833b9fee169452d3d9e1 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 29 Oct 2026 21:34:08 +0100 Subject: [PATCH 01/16] Revive Bubble uploads with current Uppy and tested release candidates --- .changeset/config.json | 12 + .changeset/revive-bubble.md | 5 + .github/dependabot.yml | 18 + .github/workflows/ci.yaml | 38 + .github/workflows/release.yaml | 33 + .gitignore | 9 + .yarnrc.yml | 2 + CONTRIBUTING.md | 90 + README.md | 71 +- actions/AAF/client.js | 37 +- actions/AAF/params.json | 26 +- biome.json | 36 + elements/AAu/initialize.js | 8 + elements/AAu/params.json | 132 ++ elements/AAu/reset.js | 7 + elements/AAu/update.js | 7 + html_headers.html | 12 +- meta_data.json | 24 +- package.json | 41 + playwright.config.mjs | 9 + scripts/build.mjs | 55 + scripts/prepare-release.mjs | 27 + shared_tech_params.json | 65 +- src/types.ts | 31 + src/uploader.ts | 212 +++ test/e2e/bubble.test.mjs | 112 ++ test/e2e/harness.mjs | 79 + test/fixtures/upload.png | Bin 0 -> 98 bytes test/unit/uploader.test.ts | 263 +++ tsconfig.json | 15 + vitest.config.mjs | 12 + yarn.lock | 3240 ++++++++++++++++++++++++++++++++ 32 files changed, 4638 insertions(+), 90 deletions(-) create mode 100644 .changeset/config.json create mode 100644 .changeset/revive-bubble.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 .yarnrc.yml create mode 100644 CONTRIBUTING.md create mode 100644 biome.json create mode 100644 elements/AAu/initialize.js create mode 100644 elements/AAu/params.json create mode 100644 elements/AAu/reset.js create mode 100644 elements/AAu/update.js create mode 100644 package.json create mode 100644 playwright.config.mjs create mode 100644 scripts/build.mjs create mode 100644 scripts/prepare-release.mjs create mode 100644 src/types.ts create mode 100644 src/uploader.ts create mode 100644 test/e2e/bubble.test.mjs create mode 100644 test/e2e/harness.mjs create mode 100644 test/fixtures/upload.png create mode 100644 test/unit/uploader.test.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.mjs create mode 100644 yarn.lock diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..136bd9d --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [], + "privatePackages": { "version": true, "tag": false } +} diff --git a/.changeset/revive-bubble.md b/.changeset/revive-bubble.md new file mode 100644 index 0000000..da3f873 --- /dev/null +++ b/.changeset/revive-bubble.md @@ -0,0 +1,5 @@ +--- +"transloadit-bubble-plugin": major +--- + +Replace deprecated Robodog with current Uppy, add a signed uploader element with Bubble states and events, and prepare tested exports for manual Bubble publication. Existing VideoWrapper and open action IDs are retained. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0f7e319 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + uppy: + patterns: ['@uppy/*'] + dev-dependencies: + dependency-type: development + update-types: [minor, patch] + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..752702f --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,38 @@ +name: CI +on: + push: + branches: [main] + pull_request: +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + strategy: + matrix: + node: ['22.23.4', '26.12.1'] + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + - run: npm install --global corepack@0.36.0 && corepack enable + - run: corepack yarn install --immutable + - run: corepack yarn check + - run: corepack yarn playwright install chromium + - run: corepack yarn test:browser + e2e: + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '26.12.1' + - run: npm install --global corepack@0.36.0 && corepack enable + - run: corepack yarn install --immutable + - run: corepack yarn playwright install chromium + - run: corepack yarn test:e2e + env: + TRANSLOADIT_KEY: ${{ secrets.BUBBLE_E2E_TRANSLOADIT_KEY }} + TRANSLOADIT_SECRET: ${{ secrets.BUBBLE_E2E_TRANSLOADIT_SECRET }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..bca4f8f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,33 @@ +name: Release candidate +on: + push: + branches: [main] +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: '26.12.1' + - run: npm install --global corepack@0.36.0 && corepack enable + - run: corepack yarn install --immutable + - uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d + with: + version: corepack yarn changeset:version:release + commitMode: github-api + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: corepack yarn release:prepare + - uses: actions/upload-artifact@v7 + with: + name: bubble-candidate-${{ github.sha }} + path: build/bubble-plugin-*.zip* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6315357 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.yarn/ +.env +.env.* +coverage/ +build/ +test-results/ +playwright-report/ +*.log diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..f37b3b1 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,2 @@ +nodeLinker: node-modules +npmMinimalAgeGate: 2880 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ec53431 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing + +## Layout and checks + +Bubble remains the primary plugin store. Keep `actions/`, `elements/`, `api/`, +`html_headers.html`, `meta_data.json` and `shared_tech_params.json` in their export layout; +never rename existing opaque IDs. New element `AAu` coexists with legacy `AAp`. + +Edit TypeScript in `src/`, then run `corepack yarn build` and commit the generated callbacks and +header. The header loads the pinned official Uppy 6.0.5 ESM/CSS CDN bundle, whose core 6.3.0, +Dashboard 6.0.2 and Transloadit 6.0.1 match the typed npm dependencies. Upgrade these together; +Dependabot cannot choose the corresponding CDN release for you. Only our glue is minified into +the header; no vendored Uppy bundle needs reviewing. Apps must allow `releases.transloadit.com` +and Transloadit's API/upload/result hosts through their CSP/network policy. + +`corepack yarn check` runs Biome, TypeScript, Vitest with enforced coverage, and deterministic +build verification. `corepack yarn fix` formats source. `corepack yarn test:browser` exercises the +actual generated header and callbacks in Chromium, through Bubble-shaped canvas/state/event APIs. +Install Chromium once with `corepack yarn playwright install chromium`. Unit tests cover lifecycle, +signature byte preservation, progress/results, failures, reset, multiple updates and legacy uploads. + +## Real API tests + +Use the opentest key from node-sdk as process environment only, without editing or copying `.env`: + +```sh +corepack yarn build +BUBBLE_REAL_API=1 node --env-file="$HOME/code/node-sdk/.env" node_modules/@playwright/test/cli.js test +``` + +This mounts the built element, selects a generated non-customer PNG, performs a signed tus upload, +waits for a real `/image/resize` result, and downloads it. It creates a billable test Assembly but +no persistent Template. The local signer keeps the Auth Secret in Node; only signed params reach +the browser. Traces and request payload recording are disabled to keep credentials out of artifacts. + +CI requires `BUBBLE_E2E_TRANSLOADIT_KEY` and `BUBBLE_E2E_TRANSLOADIT_SECRET` as Actions secrets +(and Dependabot secrets for dependency PRs). The test receives them as `TRANSLOADIT_KEY` and +`TRANSLOADIT_SECRET`. Internal PRs fail clearly when missing; fork PRs run unit/browser checks only. + +## Versioning and release candidates + +Add a user-facing Changeset with `corepack yarn changeset`. On main, Changesets opens a version PR +that updates `package.json` and `CHANGELOG.md`. Merge the version PR, then download that commit's +`bubble-candidate-` artifact from **Release candidate** Actions. The archive and SHA-256 file +contain only Bubble's export. Locally, `corepack yarn release:prepare` produces the same handoff. +A candidate is built for every main commit; only use the version PR's commit for publication. +Changesets versions are repository candidates; record Bubble's separately assigned published +version in the release notes. There is deliberately no automatic Bubble deploy or user migration. + +## Bubble handoff (Kevin) + +1. In a clean checkout of the reviewed/merged commit, run `corepack yarn install --immutable`, + `corepack yarn check`, then `corepack yarn release:prepare`. Verify the archive SHA-256 before + handing it off. Keep the prior published version available for rollback. +2. Log in to [the existing plugin editor](https://bubble.io/plugin_editor?id=1550344595465x140038650298630140). + Under **Version → GitHub**, synchronize the existing repository after the PR is merged. Bubble + pulls the export; resolve any editor/GitHub conflicts before continuing. Do not create a new plugin. + If synchronization is unavailable, the archive maps directly to tabs: Shared HTML header and + shared keys, General metadata, Elements (`AAp` and `AAu`) and Actions (`AAF`). Paste complete + function expressions from the matching generated `.js` files; declare fields/states/events + exactly as in their `params.json`. Do not replace the old element or action IDs. +3. Check **Shared → HTML header** uses the current `html_headers.html`, and confirm the new + **Transloadit Uploader** fields/states/events appear. Keep jQuery disabled; the uploader supports + both DOM and Bubble's jQuery-style canvas wrapper without depending on jQuery. +4. In the plugin editor's test-app setting, choose a disposable Bubble app, then **Go to test app** + to install/refresh this development plugin. Add the new element and an authenticated backend + signer; bind its returned params/signature strings. Never expose the Auth Secret to the page. +5. In editor and run mode, test Template ID and Instructions; inline Dashboard and button; valid + upload to completed result URLs; expired signature; type/size restriction; cancel/reset; a second + upload; two instances; and page navigation away during upload. Verify existing VideoWrapper and + open-action workflows. Repeat at desktop and 390px; check browser errors/network and CORS on any + legacy notification URL. Confirm both result URLs and Bubble workflow events, not screenshots alone. +6. Only after that pass, deploy from **Version**, selecting the appropriate change type and copying + the Changeset release notes. Verify the marketplace listing opens while signed out, install the + published version in the test app, and repeat an upload. Update README with its verified URL and + record the published Bubble version, repository commit and test evidence on #1. Then close #1. + If anything fails, keep the current published version and fix the candidate; if already deployed, + restore its saved export and publish a corrective version from the editor. + +[Bubble's GitHub integration](https://manual.bubble.io/account-and-marketplace/building-plugins/github-integration) +explains synchronization; [publishing](https://manual.bubble.io/account-and-marketplace/building-plugins/publishing-and-versioning) +is a separate editor action. These login-dependent steps remain unexecuted by CI. + +## Maintenance decisions + +Tooling follows transloadit/zapier's commits from 2026-10-14 through 2026-10-21: Yarn 4, Biome, +TypeScript, Vitest, tests of the actual built artifact, Changesets, PR CI and weekly grouped updates. +We preserve legacy IDs like Zapier preserves saved Zap contracts. Bubble has no Zapier-style CLI +upload/promote/migrate, so releases produce an export and explicit human handoff. The official CDN +keeps the export small; a future custom bundle can reduce unused Uppy plugins if download size matters. diff --git a/README.md b/README.md index a39c7d8..4f2d2e1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,70 @@ -This is a repository based on the [Bubble](https://bubble.is) plugin 'uppy-transloadit'. This lets you use Github's functionality in terms of version control, forks and pull requests. Note that the the plugin code is stored on Bubble's servers, and you need to synchronize your repository in the Bubble Plugin Editor. +# Transloadit for Bubble - This plugin can be used in [Bubble applications](https://bubble.is). \ No newline at end of file +Resumable file uploads and media processing using [Uppy](https://uppy.io) and +[Transloadit](https://transloadit.com). Add an inline Dashboard or an upload button, +then connect processed file URLs to Bubble workflows. + +## Use in a Bubble app + +The modernization is a release candidate: installation in a real Bubble app and marketplace +publication still need the maintainer's Bubble login. See [the handoff](CONTRIBUTING.md#bubble-handoff-kevin). +The existing plugin is `1550344595465x140038650298630140`; its marketplace URL must be +verified after publication before linking builders to it. + +1. Add **Transloadit Uploader** to a page (at least 350px high for an inline Dashboard). +2. Have your backend return **params** (a JSON string) and **signature** (SHA-384). + Bind those strings to **Signed Assembly params** and **Signature**. Refresh them before each + upload; signatures expire. Keep the exact params string, including spaces and key order. +3. Set **Inline Dashboard** to yes, or no for an upload button. Set allowed types, maximum files, + and maximum bytes per file. Defaults: one file, 5 MiB. Sources are local files only. +4. On **Encoding completed**, read **Result URLs** (a list of texts), **Results JSON**, or + **Assembly JSON**. Other states: **Assembly ID**, **Is uploading**, **Upload progress** (0–100, + transfer progress; processing may continue), and **Error message**. **Upload started** and + **Upload error** events allow loading and failure workflows. Bubble's reset resets the uploader. + +The `params` string supports a Template ID: + +```json +{"auth":{"key":"PUBLIC_AUTH_KEY","expires":"2026-10-30T12:00:00Z"},"template_id":"TEMPLATE_ID"} +``` + +Or replace `template_id` with `steps` containing your Assembly Instructions. Generate it and +its signature on a trusted server, for example with Node: + +```js +import { createHmac } from 'node:crypto' +const params = JSON.stringify({ + auth: { key: process.env.TRANSLOADIT_KEY, expires: new Date(Date.now() + 300_000).toISOString() }, + template_id: 'YOUR_TEMPLATE_ID', +}) +const signature = `sha384:${createHmac('sha384', process.env.TRANSLOADIT_SECRET).update(params).digest('hex')}` +// Return { params, signature } through your authenticated backend endpoint. +``` + +Never put an Auth Secret in Bubble client properties, shared client-safe keys, HTML, or browser code. +Your backend must authorize the request and choose its allowed Template/Instructions rather than +sign arbitrary user-submitted params. See [signature authentication](https://transloadit.com/docs/api/signature-authentication/). + +## Existing apps + +`VideoWrapper` and its `src` field keep their IDs and behavior. The global **open** action now opens +current Uppy; its existing Auth Key, Template ID, maximum file size, allowed types and notification +URL settings still work. Its optional params/signature inputs enable signed uploads. Legacy +unsigned uploads need a key that permits them; migrate to signed uploads. Notifications still POST +an array of completed Assemblies from the browser (the receiver needs CORS); server notifications +can instead use `notify_url` inside signed params. Old remote-source settings are retained but +unused; this version offers local file selection without Companion/OAuth configuration. + +## Develop + +Use Node 22.23.4+ (CI also tests 26.12.1) and Yarn 4.12 via Corepack: + +```sh +corepack yarn install +corepack yarn build +corepack yarn check +corepack yarn test:browser +``` + +Read [CONTRIBUTING.md](CONTRIBUTING.md) for the source/build layout, real-API tests, +maintenance decisions and release process. Git commits and CI artifacts do not publish to Bubble. diff --git a/actions/AAF/client.js b/actions/AAF/client.js index a9b8bc3..5f34db1 100644 --- a/actions/AAF/client.js +++ b/actions/AAF/client.js @@ -1,34 +1,5 @@ function(properties, context) { - //Load any data - - let restrictions = { - maxNumberOfFiles: 1, - maxFileSize: parseInt(context.keys['MAX FILE SIZE']) || 5242880 - } - if(context.keys['ALLOWED FILE TYPES']){ - restrictions.allowedFileTypes = context.keys['ALLOWED FILE TYPES'].split(" ") - } - - Robodog.pick({ - target: 'body', - restrictions: restrictions, - params: { - auth: { key: context.keys['AUTH KEY'] }, - template_id: context.keys['TEMPLATE ID'] - }, - waitForEncoding: true, - providers: context.keys['ALLOWED SOURCES'] ? context.keys['ALLOWED SOURCES'].split(" ") : ['url', 'webcam', 'dropbox', 'google-drive', 'instagram'] - }) - .then((bundle) => { - fetch(context.keys['NOTIFY URL'], { - method: 'post', - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(bundle.transloadit) - }).then(res => res.json()) - .then(res => console.log(res)); - - }) -} \ No newline at end of file + // Generated by yarn build. + window.TransloaditBubbleReady.then(function(api) { api.open(properties, context); }) + .catch(function(error) { context.reportDebugger(error.message); }); +} diff --git a/actions/AAF/params.json b/actions/AAF/params.json index 476d629..30a7896 100644 --- a/actions/AAF/params.json +++ b/actions/AAF/params.json @@ -1,5 +1,23 @@ { - "category": "plugins", - "display": "open", - "type": "client_side" -} \ No newline at end of file + "category": "plugins", + "display": "open", + "type": "client_side", + "fields": { + "ABM": { + "name": "params", + "caption": "Signed Assembly params (JSON)", + "editor": "DynamicValue", + "value": "text", + "optional": true, + "rank": 0 + }, + "ABN": { + "name": "signature", + "caption": "Signature (server-generated)", + "editor": "DynamicValue", + "value": "text", + "optional": true, + "rank": 1 + } + } +} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..439546f --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": [ + "**", + "!actions/**/*.js", + "!elements/**/*.js", + "!html_headers.html", + "!api", + "!.changeset/*.md", + "!CHANGELOG.md", + "!elements/AAp" + ] + }, + "formatter": { + "indentStyle": "space", + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/elements/AAu/initialize.js b/elements/AAu/initialize.js new file mode 100644 index 0000000..71bd562 --- /dev/null +++ b/elements/AAu/initialize.js @@ -0,0 +1,8 @@ +function(instance, context) { + // Generated by yarn build. + instance.data.ready = window.TransloaditBubbleReady.then(function(api) { api.initialize(instance, context); return api; }); + instance.data.ready.catch(function(error) { + instance.publishState('error_message', error.message); + instance.triggerEvent('error'); + }); +} diff --git a/elements/AAu/params.json b/elements/AAu/params.json new file mode 100644 index 0000000..98f956d --- /dev/null +++ b/elements/AAu/params.json @@ -0,0 +1,132 @@ +{ + "add_is_visible": true, + "category": "visual elements", + "default_dim": { + "height": 350, + "width": 459 + }, + "display": "Transloadit Uploader", + "icon": "ion-upload", + "support_autobinding": false, + "property_applications": { + "resizable": true, + "MinWidth": true + }, + "fields": { + "AAv": { + "name": "params", + "caption": "Signed Assembly params (JSON)", + "editor": "DynamicValue", + "value": "text", + "default_val": "", + "optional": true, + "rank": 0 + }, + "AAw": { + "name": "signature", + "caption": "Signature (server-generated)", + "editor": "DynamicValue", + "value": "text", + "default_val": "", + "optional": true, + "rank": 1 + }, + "AAx": { + "name": "inline_dashboard", + "caption": "Inline Dashboard", + "editor": "DynamicValue", + "value": "boolean", + "default_val": true, + "optional": true, + "rank": 2 + }, + "AAy": { + "name": "button_label", + "caption": "Upload button label", + "editor": "DynamicValue", + "value": "text", + "default_val": "Upload files", + "optional": true, + "rank": 3 + }, + "AAz": { + "name": "max_files", + "caption": "Maximum files", + "editor": "DynamicValue", + "value": "number", + "default_val": 1, + "optional": true, + "rank": 4 + }, + "ABA": { + "name": "max_file_size", + "caption": "Maximum file size (bytes)", + "editor": "DynamicValue", + "value": "number", + "default_val": 5242880, + "optional": true, + "rank": 5 + }, + "ABB": { + "name": "allowed_file_types", + "caption": "Allowed types (comma or space separated)", + "editor": "DynamicValue", + "value": "text", + "default_val": "", + "optional": true, + "rank": 6 + } + }, + "states": { + "ABC": { + "name": "is_uploading", + "caption": "Is uploading", + "value": "boolean" + }, + "ABD": { + "name": "progress", + "caption": "Upload progress", + "value": "number" + }, + "ABE": { + "name": "assembly_id", + "caption": "Assembly ID", + "value": "text" + }, + "ABF": { + "name": "assembly_json", + "caption": "Assembly JSON", + "value": "text" + }, + "ABG": { + "name": "results_json", + "caption": "Results JSON", + "value": "text" + }, + "ABH": { + "name": "result_urls", + "caption": "Result URLs", + "value": "text", + "is_list": true + }, + "ABI": { + "name": "error_message", + "caption": "Error message", + "value": "text" + } + }, + "events": { + "ABJ": { + "name": "upload_started", + "caption": "Upload started" + }, + "ABK": { + "name": "completed", + "caption": "Encoding completed" + }, + "ABL": { + "name": "error", + "caption": "Upload error" + } + } +} diff --git a/elements/AAu/reset.js b/elements/AAu/reset.js new file mode 100644 index 0000000..3948d74 --- /dev/null +++ b/elements/AAu/reset.js @@ -0,0 +1,7 @@ +function(instance, context) { + // Generated by yarn build. + instance.data.ready.then(function(api) { api.reset(instance, context); }).catch(function(error) { + instance.publishState('error_message', error.message); + instance.triggerEvent('error'); + }); +} diff --git a/elements/AAu/update.js b/elements/AAu/update.js new file mode 100644 index 0000000..83f1fbd --- /dev/null +++ b/elements/AAu/update.js @@ -0,0 +1,7 @@ +function(instance, properties, context) { + // Generated by yarn build. + instance.data.ready.then(function(api) { api.update(instance, properties, context); }).catch(function(error) { + instance.publishState('error_message', error.message); + instance.triggerEvent('error'); + }); +} diff --git a/html_headers.html b/html_headers.html index 9f0bee8..dcf435b 100644 --- a/html_headers.html +++ b/html_headers.html @@ -1,2 +1,10 @@ - - \ No newline at end of file + + diff --git a/meta_data.json b/meta_data.json index 9c7675e..4b9704d 100644 --- a/meta_data.json +++ b/meta_data.json @@ -1,13 +1,13 @@ { - "categories": { - "0": "technical" - }, - "demo_page": "https://bubble.is/page?name=index&id=bubtran&test_plugin=1550344595465x140038650298630140_current&tab=tabs-1", - "description": "A Plugin that enables you to upload files to Transloadit for encoding and returns a response via your notification URL", - "image": "//s3.amazonaws.com/appforest_uf/f1551773910041x381779185758073900/logo.svg", - "instructions": "", - "license": "open_source", - "link": "https://transloadit.com", - "name": "uppy-transloadit", - "plugin_instructions": "Please input your transloadit auth key in the settings page alongside your template ID. \n\nAlso, hook the open action of the plugin to a button, so the modal to select files is shown up." -} \ No newline at end of file + "categories": { + "0": "technical" + }, + "demo_page": "https://bubble.is/page?name=index&id=bubtran&test_plugin=1550344595465x140038650298630140_current&tab=tabs-1", + "description": "Resumable file uploads and media processing with Uppy and Transloadit. Embed a Dashboard or upload button, use server-signed Templates or Assembly Instructions, and connect results to Bubble workflows.", + "image": "//s3.amazonaws.com/appforest_uf/f1551773910041x381779185758073900/logo.svg", + "instructions": "Add Transloadit Uploader to a page. Supply params JSON and its matching server-generated SHA-384 signature. Choose inline Dashboard or an upload button. Read Result URLs and Results JSON after Encoding completed. Never put an Auth Secret in client-side properties. See the repository README for setup.", + "license": "open_source", + "link": "https://transloadit.com", + "name": "uppy-transloadit", + "plugin_instructions": "Add Transloadit Uploader to a page. Supply params JSON and its matching server-generated SHA-384 signature. Choose inline Dashboard or an upload button. Read Result URLs and Results JSON after Encoding completed. Never put an Auth Secret in client-side properties. See the repository README for setup." +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c40992 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "transloadit-bubble-plugin", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "Uppy and Transloadit for Bubble", + "repository": "transloadit/bubble-plugin", + "license": "MIT", + "packageManager": "yarn@4.12.0", + "engines": { + "node": ">=22.23.4" + }, + "scripts": { + "build": "node scripts/build.mjs", + "check": "yarn lint && yarn test && yarn build && git diff --exit-code -- actions elements html_headers.html", + "lint": "biome check . && tsc --noEmit", + "fix": "biome check --write .", + "test": "vitest run --coverage", + "test:browser": "yarn build && playwright test", + "test:e2e": "yarn build && BUBBLE_REAL_API=1 playwright test", + "changeset": "changeset", + "changeset:version:release": "changeset version", + "release:prepare": "yarn check && node scripts/prepare-release.mjs" + }, + "dependencies": { + "@uppy/core": "6.3.0", + "@uppy/dashboard": "6.0.2", + "@uppy/transloadit": "6.0.1" + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@changesets/cli": "^2.31.0", + "@playwright/test": "1.64.1", + "@types/node": "^22", + "@vitest/coverage-v8": "4.1.11", + "esbuild": "0.28.2", + "jsdom": "^28.1.0", + "typescript": "6.0.3", + "vitest": "4.1.11" + } +} diff --git a/playwright.config.mjs b/playwright.config.mjs new file mode 100644 index 0000000..6ceffc0 --- /dev/null +++ b/playwright.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: './test/e2e', + workers: 1, + timeout: 120_000, + use: { browserName: 'chromium', headless: true, trace: 'off' }, + reporter: 'list', +}) diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100644 index 0000000..1fbdc98 --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,55 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises' + +import { build } from 'esbuild' + +// Uppy 6.0.5 ships the exact core/dashboard/transloadit versions in package.json. +const cdn = 'https://releases.transloadit.com/uppy/v6.0.5/uppy.min' +const modules = { + '@uppy/core': ['Uppy', '6.3.0'], + '@uppy/dashboard': ['Dashboard', '6.0.2'], + '@uppy/transloadit': ['Transloadit', '6.0.1'], +} +const { dependencies } = JSON.parse(await readFile('package.json', 'utf8')) +for (const [name, [, version]] of Object.entries(modules)) { + if (dependencies[name] !== version) + throw new Error(`Update the official Uppy CDN release alongside ${name}`) +} +const { outputFiles } = await build({ + entryPoints: ['src/uploader.ts'], + bundle: true, + write: false, + minify: true, + target: 'es2022', + format: 'iife', + globalName: 'TransloaditBubble', + plugins: [ + { + name: 'official-uppy-cdn', + setup(build) { + build.onResolve({ filter: /^@uppy\// }, ({ path }) => ({ path, namespace: 'uppy' })) + build.onLoad({ filter: /.*/, namespace: 'uppy' }, ({ path }) => ({ + contents: `export default vendor.${modules[path][0]}`, + })) + }, + }, + ], +}) +await writeFile( + 'html_headers.html', + `\n\n`, +) +await mkdir('elements/AAu', { recursive: true }) +for (const method of ['initialize', 'update', 'reset']) { + const args = method === 'update' ? 'instance, properties, context' : 'instance, context' + const ready = method === 'initialize' ? 'window.TransloaditBubbleReady' : 'instance.data.ready' + const call = + method === 'initialize' ? `api.${method}(${args}); return api;` : `api.${method}(${args});` + await writeFile( + `elements/AAu/${method}.js`, + `function(${args}) {\n // Generated by yarn build.\n ${method === 'initialize' ? 'instance.data.ready = ' : ''}${ready}.then(function(api) { ${call} })${method === 'initialize' ? ';\n instance.data.ready' : ''}.catch(function(error) {\n instance.publishState('error_message', error.message);\n instance.triggerEvent('error');\n });\n}\n`, + ) +} +await writeFile( + 'actions/AAF/client.js', + `function(properties, context) {\n // Generated by yarn build.\n window.TransloaditBubbleReady.then(function(api) { api.open(properties, context); })\n .catch(function(error) { context.reportDebugger(error.message); });\n}\n`, +) diff --git a/scripts/prepare-release.mjs b/scripts/prepare-release.mjs new file mode 100644 index 0000000..db754d9 --- /dev/null +++ b/scripts/prepare-release.mjs @@ -0,0 +1,27 @@ +import { execFileSync } from 'node:child_process' +import { createHash } from 'node:crypto' +import { mkdir, readFile, writeFile } from 'node:fs/promises' + +const { version } = JSON.parse(await readFile('package.json', 'utf8')) +await mkdir('build', { recursive: true }) +const file = `build/bubble-plugin-${version}.zip` +// Only Bubble's export goes in the handoff, never the checkout or its environment. +const entries = [ + 'actions', + 'elements', + 'api', + 'html_headers.html', + 'meta_data.json', + 'shared_tech_params.json', +] +// Remove an earlier archive through unlink so zip cannot retain stale entries. +const { unlink } = await import('node:fs/promises') +await unlink(file).catch((error) => { + if (error.code !== 'ENOENT') throw error +}) +execFileSync('zip', ['-qr', file, ...entries]) +const sha256 = createHash('sha256') + .update(await readFile(file)) + .digest('hex') +await writeFile(`${file}.sha256`, `${sha256} ${file.split('/').pop()}\n`) +console.log(`Bubble candidate: ${file} (${sha256}). Install and publish using CONTRIBUTING.md.`) diff --git a/shared_tech_params.json b/shared_tech_params.json index d53324b..d44ed0a 100644 --- a/shared_tech_params.json +++ b/shared_tech_params.json @@ -1,41 +1,32 @@ { - "assets": { - "AAG": { - "name": "robodogjs", - "url": "//dd7tel2830j4w.cloudfront.net/f1550425305140x505543046492519040/transloadit.min.js" - }, - "AAH": { - "name": "robodogcss", - "url": "//dd7tel2830j4w.cloudfront.net/f1550425323424x127923606973563420/style.min.css" - } + "assets": {}, + "dependencies": { + "use_jquery": false + }, + "shared_keys": { + "AAR": { + "caption": "TEMPLATE ID", + "type": "client_safe" }, - "dependencies": { - "use_jquery": false + "AAS": { + "caption": "AUTH KEY", + "type": "client_safe" }, - "shared_keys": { - "AAR": { - "caption": "TEMPLATE ID", - "type": "client_safe" - }, - "AAS": { - "caption": "AUTH KEY", - "type": "client_safe" - }, - "AAl": { - "caption": "NOTIFY URL", - "type": "client_safe" - }, - "AAr": { - "caption": "MAX FILE SIZE", - "type": "client_safe" - }, - "AAs": { - "caption": "ALLOWED FILE TYPES", - "type": "client_safe" - }, - "AAt": { - "caption": "ALLOWED SOURCES", - "type": "client_safe" - } + "AAl": { + "caption": "NOTIFY URL", + "type": "client_safe" + }, + "AAr": { + "caption": "MAX FILE SIZE", + "type": "client_safe" + }, + "AAs": { + "caption": "ALLOWED FILE TYPES", + "type": "client_safe" + }, + "AAt": { + "caption": "ALLOWED SOURCES", + "type": "client_safe" } -} \ No newline at end of file + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a12e88e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,31 @@ +import type Uppy from '@uppy/core' + +export interface Properties { + params?: string + signature?: string + inline_dashboard?: boolean + button_label?: string + max_files?: number + max_file_size?: number + allowed_file_types?: string +} +export interface Context { + keys: Record + reportDebugger: (message: string) => void +} +export interface Instance { + canvas: HTMLElement | { 0: HTMLElement } + data: { + uppy?: Uppy + properties?: Properties + root?: HTMLElement + observer?: MutationObserver + dispose?: () => void + mode?: boolean + isUploading?: boolean + failed?: boolean + legacy?: boolean + } + publishState: (name: string, value: unknown) => void + triggerEvent: (name: string) => void +} diff --git a/src/uploader.ts b/src/uploader.ts new file mode 100644 index 0000000..7480754 --- /dev/null +++ b/src/uploader.ts @@ -0,0 +1,212 @@ +import Uppy from '@uppy/core' +import Dashboard from '@uppy/dashboard' +import Transloadit from '@uppy/transloadit' + +import type { Context, Instance, Properties } from './types' + +const empty = () => ({ + is_uploading: false, + progress: 0, + assembly_id: '', + assembly_json: '', + results_json: '', + result_urls: [] as string[], + error_message: '', +}) +function publish(instance: Instance, values: Record) { + for (const [name, value] of Object.entries(values)) instance.publishState(name, value) +} +export function fail(instance: Instance, error: unknown) { + instance.data.failed = true + instance.data.isUploading = false + publish(instance, { + is_uploading: false, + error_message: error instanceof Error ? error.message : String(error), + }) + instance.triggerEvent('error') +} +function positive(value: number | undefined, fallback: number) { + const number = value ?? fallback + if (!Number.isSafeInteger(number) || number <= 0) + throw new Error('Upload limits must be positive integers') + return number +} +export function assemblyOptions(properties: Properties, signed = true) { + const params = properties.params ?? '' + const parsed = JSON.parse(params) + if (!parsed?.auth?.key || !(parsed.template_id || parsed.steps)) { + throw new Error('Assembly params need auth.key and template_id or steps') + } + if (signed && (!properties.signature || !parsed.auth.expires)) { + throw new Error('Provide server-signed params with auth.expires and a signature') + } + // Do not reserialize: signatures authenticate the exact bytes supplied by the server. + return { params, signature: properties.signature || undefined } +} +export function initialize(instance: Instance, _context: Context) { + instance.data.dispose?.() + publish(instance, empty()) + const canvas = '0' in instance.canvas ? instance.canvas[0] : instance.canvas + const root = document.createElement('div') + root.style.cssText = 'width:100%;height:100%;min-width:0' + canvas.appendChild(root) + instance.data.root = root + instance.data.dispose = () => { + instance.data.observer?.disconnect() + instance.data.uppy?.destroy() + instance.data.uppy = undefined + root.remove() + } + instance.data.observer = new MutationObserver(() => { + if (!root.isConnected) instance.data.dispose?.() + }) + instance.data.observer.observe(document.body, { childList: true, subtree: true }) +} +export function update(instance: Instance, properties: Properties, _context: Context) { + try { + const restrictions = { + maxNumberOfFiles: positive(properties.max_files, 1), + maxFileSize: positive(properties.max_file_size, 5 * 1024 * 1024), + allowedFileTypes: properties.allowed_file_types?.split(/[\s,]+/).filter(Boolean) ?? null, + } + instance.data.properties = properties + const inline = properties.inline_dashboard !== false + if (!instance.data.uppy || (inline !== instance.data.mode && !instance.data.isUploading)) { + instance.data.uppy?.destroy() + const root = instance.data.root + if (!root) throw new Error('Initialize the uploader before updating it') + root.replaceChildren() + const uppy = new Uppy({ restrictions, autoProceed: false }) + .use(Transloadit, { + waitForEncoding: true, + errorReporting: false, + clientName: 'bubble-plugin', + assemblyOptions: () => + assemblyOptions(instance.data.properties ?? {}, !instance.data.legacy), + }) + .use( + Dashboard, + inline + ? { target: root, inline: true, width: '100%', height: '100%' } + : { target: root, inline: false, closeAfterFinish: true }, + ) + instance.data.uppy = uppy + instance.data.mode = inline + if (!inline) { + const button = document.createElement('button') + button.type = 'button' + button.textContent = properties.button_label || 'Upload files' + button.onclick = () => { + void uppy.getPlugin('Dashboard')?.openModal() + } + root.appendChild(button) + } + uppy.on('upload', () => { + instance.data.failed = false + instance.data.isUploading = true + publish(instance, { ...empty(), is_uploading: true }) + instance.triggerEvent('upload_started') + }) + uppy.on('progress', (progress) => instance.publishState('progress', progress)) + uppy.on('transloadit:assembly-created', (assembly) => + instance.publishState('assembly_id', assembly.assembly_id), + ) + uppy.on('transloadit:complete', (assembly) => { + const results = assembly.results ?? {} + const urls = Object.values(results) + .flat() + .flatMap((file) => (file.ssl_url ? [file.ssl_url] : [])) + publish(instance, { + assembly_id: assembly.assembly_id, + assembly_json: JSON.stringify(assembly), + results_json: JSON.stringify(results), + result_urls: urls, + }) + }) + uppy.on('complete', (result) => { + instance.data.isUploading = false + instance.publishState('is_uploading', false) + if (!instance.data.failed && !result.failed?.length) { + instance.publishState('progress', 100) + instance.triggerEvent('completed') + } + }) + uppy.on('error', (error) => fail(instance, error)) + uppy.on('upload-error', (_file, error) => fail(instance, error)) + uppy.on('restriction-failed', (_file, error) => fail(instance, error)) + uppy.on('transloadit:assembly-error', (_assembly, error) => fail(instance, error)) + uppy.on('cancel-all', () => resetStates(instance)) + } else { + instance.data.uppy.setOptions({ restrictions }) + const button = instance.data.root?.querySelector('button') + if (!instance.data.mode && button) + button.textContent = properties.button_label || 'Upload files' + } + } catch (error) { + fail(instance, error) + } +} +function resetStates(instance: Instance) { + instance.data.isUploading = false + instance.data.failed = false + publish(instance, empty()) +} +export function reset(instance: Instance) { + instance.data.uppy?.cancelAll() + resetStates(instance) +} +export function open(properties: Properties, context: Context) { + // Preserve the global action and its existing keys; new elements require signed params. + const canvas = document.createElement('div') + document.body.appendChild(canvas) + const instance: Instance = { + canvas, + data: { legacy: !properties.params }, + publishState: () => {}, + triggerEvent: (event) => { + if (event === 'error') + context.reportDebugger('Transloadit upload failed; check the Dashboard') + }, + } + initialize(instance, context) + update( + instance, + { + ...properties, + params: + properties.params || + JSON.stringify({ + auth: { key: context.keys['AUTH KEY'] }, + template_id: context.keys['TEMPLATE ID'], + }), + inline_dashboard: false, + max_file_size: Number(context.keys['MAX FILE SIZE']) || undefined, + allowed_file_types: context.keys['ALLOWED FILE TYPES'], + }, + context, + ) + const uppy = instance.data.uppy + const dispose = () => { + instance.data.dispose?.() + canvas.remove() + } + if (!uppy) { + dispose() + return + } + uppy.on('transloadit:complete', (assembly) => { + const url = context.keys['NOTIFY URL'] + if (url) + void fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([assembly]), + }) + .then((response) => { + if (!response.ok) throw new Error(`Notification failed: HTTP ${response.status}`) + }) + .catch(() => context.reportDebugger('Transloadit notification failed')) + }) + uppy.on('dashboard:modal-closed', dispose) + void uppy.getPlugin('Dashboard')?.openModal() +} diff --git a/test/e2e/bubble.test.mjs b/test/e2e/bubble.test.mjs new file mode 100644 index 0000000..fcd8592 --- /dev/null +++ b/test/e2e/bubble.test.mjs @@ -0,0 +1,112 @@ +import { expect, test } from '@playwright/test' + +import { startHarness } from './harness.mjs' + +const real = process.env.BUBBLE_REAL_API === '1' +if (real && (!process.env.TRANSLOADIT_KEY || !process.env.TRANSLOADIT_SECRET)) { + throw new Error( + 'Real API tests require TRANSLOADIT_KEY and TRANSLOADIT_SECRET (CI: BUBBLE_E2E_TRANSLOADIT_KEY and BUBBLE_E2E_TRANSLOADIT_SECRET)', + ) +} +let harness +const errors = new WeakMap() +test.beforeAll(async () => { + harness = await startHarness() +}) +test.afterAll(async () => { + await harness?.close() +}) +test.beforeEach(async ({ page }) => { + const issues = [] + errors.set(page, issues) + page.on('pageerror', (error) => issues.push(error.message)) + page.on('console', (message) => { + if (message.type() === 'error') issues.push(message.text()) + }) + page.on('requestfailed', (request) => + issues.push(`Failed request: ${new URL(request.url()).origin}`), + ) + await page.goto(harness.url) +}) +test.afterEach(async ({ page }) => { + // Request payloads and traces are deliberately not recorded: they contain signed params. + expect(errors.get(page)).toEqual([]) +}) + +for (const width of [1280, 390]) { + test(`renders built callbacks at ${width}px and retains files on Bubble updates`, async ({ + page, + }) => { + await page.setViewportSize({ width, height: 800 }) + await page.evaluate(async () => { + await window.mount({ inline_dashboard: true }) + }) + await expect(page.getByRole('button', { name: 'browse files' })).toBeVisible() + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + await expect(page.getByText('upload.png', { exact: true })).toBeVisible() + await page.evaluate(() => { + window.updateElement(window.instance, { inline_dashboard: true }, window.context) + }) + await expect(page.getByText('upload.png', { exact: true })).toBeVisible() + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + await page.evaluate(() => window.resetElement(window.instance, window.context)) + await expect(page.getByText('upload.png', { exact: true })).toHaveCount(0) + }) +} + +test('opens and closes the button Dashboard', async ({ page }) => { + await page.evaluate(async () => { + await window.mount({ inline_dashboard: false, button_label: 'Choose files' }) + }) + await page.getByRole('button', { name: 'Choose files', exact: true }).click() + await expect(page.getByRole('button', { name: 'browse files' })).toBeVisible() + await page.getByRole('button', { name: 'Close Modal' }).click() + await expect(page.getByRole('button', { name: 'browse files' })).toBeHidden() +}) + +test('publishes a visible configuration error and recovers after reset', async ({ page }) => { + await page.evaluate(async () => { + await window.mount({ inline_dashboard: true }) + }) + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + await page.getByRole('button', { name: /Upload 1 file/ }).click() + await expect.poll(() => page.evaluate(() => window.events.includes('error'))).toBe(true) + expect(await page.evaluate(() => window.states.error_message.length > 0)).toBe(true) + await page.evaluate(() => window.resetElement(window.instance, window.context)) + expect(await page.evaluate(() => window.states.error_message)).toBe('') +}) + +test('uploads a real file and exposes real processed results from the built Bubble export', async ({ + page, + request, +}) => { + test.skip(!real, 'Run yarn test:e2e with opentest credentials') + await page.evaluate(async () => { + const options = await fetch('/options').then((response) => response.json()) + await window.mount({ ...options, inline_dashboard: true }) + }) + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + await page.getByRole('button', { name: /Upload 1 file/ }).click() + await expect + .poll(() => page.evaluate(() => window.events.includes('completed')), { timeout: 100_000 }) + .toBe(true) + expect(await page.evaluate(() => window.events.includes('error'))).toBe(false) + const result = await page.evaluate(() => { + const assembly = JSON.parse(window.states.assembly_json) + const file = JSON.parse(window.states.results_json).resize[0] + return { + status: assembly.ok, + count: assembly.uploads.length, + width: file.meta.width, + height: file.meta.height, + url: window.states.result_urls[0], + } + }) + expect(result.status).toBe('ASSEMBLY_COMPLETED') + expect(result.count).toBe(1) + expect(result.width).toBe(16) + expect(result.height).toBe(16) + const download = await request.get(result.url) + expect(download.ok()).toBe(true) + expect((await download.body()).length).toBeGreaterThan(0) +}) diff --git a/test/e2e/harness.mjs b/test/e2e/harness.mjs new file mode 100644 index 0000000..9245ad8 --- /dev/null +++ b/test/e2e/harness.mjs @@ -0,0 +1,79 @@ +import { execFileSync } from 'node:child_process' +import { createHmac } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { createServer } from 'node:http' + +export async function startHarness() { + const headers = await readFile('html_headers.html', 'utf8') + const server = createServer(async (request, response) => { + try { + const path = new URL(request.url, 'http://localhost').pathname + if (path === '/options') { + const { TRANSLOADIT_KEY: key, TRANSLOADIT_SECRET: secret } = process.env + if (!key || !secret) { + response.writeHead(503).end() + return + } + const params = JSON.stringify({ + auth: { key, expires: new Date(Date.now() + 300_000).toISOString() }, + steps: { + resize: { + robot: '/image/resize', + use: ':original', + width: 16, + height: 16, + result: true, + }, + }, + }) + const signature = `sha384:${createHmac('sha384', secret).update(params).digest('hex')}` + response.setHeader('Content-Type', 'application/json') + response.end(JSON.stringify({ params, signature })) + } else if (/^\/elements\/AAu\/(initialize|update|reset)\.js$/.test(path)) { + response.setHeader('Content-Type', 'text/plain') + response.end(await readFile(`.${path}`, 'utf8')) + } else if (path === '/before') { + const baseline = execFileSync('git', ['show', 'origin/main:html_headers.html'], { + encoding: 'utf8', + }) + const action = execFileSync('git', ['show', 'origin/main:actions/AAF/client.js'], { + encoding: 'utf8', + }) + response.setHeader('Content-Type', 'text/html') + response.end(`${baseline}`) + } else if (path === '/') { + response.setHeader('Content-Type', 'text/html') + response.end(`${headers} +
+ `) + } else { + response.writeHead(404).end() + } + } catch { + response.writeHead(500).end('Harness error') + } + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + return { + url: `http://127.0.0.1:${server.address().port}`, + close: () => new Promise((resolve) => server.close(resolve)), + } +} diff --git a/test/fixtures/upload.png b/test/fixtures/upload.png new file mode 100644 index 0000000000000000000000000000000000000000..d366955104e3f3e4a75ddcc4384053d8c87a6473 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WG(BA$Ln`LHJ?qHHz`((>p*z{H vJDW@XP{RMzU4E_#0vs$%jSdO|HJoBB5lf<_+5EYInixD?{an^LB{Ts5G`| { + const clients: MockUppy[] = [] + class MockUppy { + events = new Map void)[]>() + plugins = new Map() + destroyed = false + opts: Record + constructor(opts: Record) { + this.opts = opts + clients.push(this) + } + use(plugin: { name: string }, opts: Record) { + this.plugins.set(plugin.name, { opts, openModal: vi.fn(), setOptions: vi.fn() }) + return this + } + on(name: string, fn: (...args: unknown[]) => void) { + this.events.set(name, [...(this.events.get(name) ?? []), fn]) + return this + } + emit(name: string, ...args: unknown[]) { + for (const fn of this.events.get(name) ?? []) fn(...args) + } + setOptions = vi.fn() + getPlugin(name: string) { + return this.plugins.get(name) + } + cancelAll = vi.fn() + destroy() { + this.destroyed = true + } + } + return { clients, MockUppy } +}) +vi.mock('@uppy/core', () => ({ default: MockUppy })) +vi.mock('@uppy/dashboard', () => ({ default: { name: 'Dashboard' } })) +vi.mock('@uppy/transloadit', () => ({ default: { name: 'Transloadit' } })) + +const context: Context = { keys: {}, reportDebugger: vi.fn() } +const properties = { + params: + '{ "auth": {"key":"public-key","expires":"2030/01/01 00:00:00+00:00"}, "template_id":"template" }', + signature: 'sha384:test-signature', + inline_dashboard: true, +} +let instance: Instance +let states: Record +beforeEach(() => { + clients.length = 0 + states = {} + const canvas = document.createElement('div') + document.body.replaceChildren(canvas) + instance = { + canvas: { 0: canvas }, + data: {}, + publishState: vi.fn((name, value) => { + states[name] = value + }), + triggerEvent: vi.fn(), + } +}) + +afterEach(() => instance.data.dispose?.()) + +describe('Bubble uploader lifecycle', () => { + it('initializes Bubble states and mounts one uploader across repeated updates', () => { + initialize(instance, context) + expect(states).toMatchObject({ is_uploading: false, progress: 0, result_urls: [] }) + update(instance, properties, context) + update(instance, { ...properties }, context) + expect(clients).toHaveLength(1) + expect(instance.data.root?.isConnected).toBe(true) + }) + + it('keeps the exact signed params string and reads refreshed signatures per upload', async () => { + initialize(instance, context) + update(instance, properties, context) + const options = clients[0].plugins.get('Transloadit').opts + expect(await options.assemblyOptions()).toEqual({ + params: properties.params, + signature: properties.signature, + }) + update(instance, { ...properties, signature: 'sha384:fresh' }, context) + expect(await options.assemblyOptions()).toEqual({ + params: properties.params, + signature: 'sha384:fresh', + }) + expect(options.waitForEncoding).toBe(true) + }) +}) + +describe('upload states and failures', () => { + beforeEach(() => { + initialize(instance, context) + update(instance, properties, context) + }) + + it('publishes real results before signaling completion, and clears stale results on the next upload', () => { + const client = clients[0] + client.emit('upload') + expect(states.is_uploading).toBe(true) + expect(instance.triggerEvent).toHaveBeenCalledWith('upload_started') + client.emit('progress', 64) + expect(states.progress).toBe(64) + client.emit('transloadit:assembly-created', { assembly_id: 'assembly-1' }) + expect(states.assembly_id).toBe('assembly-1') + const assembly = { + assembly_id: 'assembly-1', + results: { thumb: [{ ssl_url: 'https://example.com/file.png' }, {}] }, + } + client.emit('transloadit:complete', assembly) + expect(states.result_urls).toEqual(['https://example.com/file.png']) + expect(JSON.parse(String(states.assembly_json))).toEqual(assembly) + expect(JSON.parse(String(states.results_json))).toEqual(assembly.results) + client.emit('complete', { failed: [] }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + expect(states.is_uploading).toBe(false) + expect(states.progress).toBe(100) + client.emit('upload') + expect(states.result_urls).toEqual([]) + expect(states.assembly_json).toBe('') + }) + + it.each([ + 'error', + 'upload-error', + 'restriction-failed', + 'transloadit:assembly-error', + ])('publishes %s without a false completed event', (event) => { + const client = clients[0] + client.emit('upload') + const error = new Error('API request failed') + client.emit(event, ...(event === 'error' ? [error] : [null, error])) + client.emit('complete', { failed: [] }) + expect(states.error_message).toBe('API request failed') + expect(states.is_uploading).toBe(false) + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + }) + + it('does not signal completed for an upload result with failed files', () => { + clients[0].emit('complete', { failed: [{}] }) + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + }) + + it('cancels uploads and resets all states', () => { + clients[0].emit('upload') + reset(instance) + expect(clients[0].cancelAll).toHaveBeenCalledOnce() + expect(states).toMatchObject({ is_uploading: false, progress: 0, error_message: '' }) + clients[0].emit('cancel-all') + expect(states.result_urls).toEqual([]) + }) + + it('releases Uppy and its DOM observer when Bubble removes the element', async () => { + document.body.replaceChildren() + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(clients[0].destroyed).toBe(true) + expect(instance.data.uppy).toBeUndefined() + }) + + it('supports the button/modal mode, updates its label and retains the uploader on property changes', () => { + update( + instance, + { ...properties, inline_dashboard: false, button_label: 'Choose photo' }, + context, + ) + const client = clients[1] + expect(clients[0].destroyed).toBe(true) + const button = instance.data.root?.querySelector('button') + expect(button?.textContent).toBe('Choose photo') + button?.click() + expect(client.getPlugin('Dashboard').openModal).toHaveBeenCalledOnce() + update( + instance, + { + ...properties, + inline_dashboard: false, + button_label: 'Add file', + max_files: 3, + allowed_file_types: '.png, image/jpeg', + }, + context, + ) + expect(button?.textContent).toBe('Add file') + expect(client.setOptions).toHaveBeenCalledWith({ + restrictions: { + maxNumberOfFiles: 3, + maxFileSize: 5242880, + allowedFileTypes: ['.png', 'image/jpeg'], + }, + }) + }) + + it('does not destroy an active upload when Bubble changes the display mode', () => { + clients[0].emit('upload') + update(instance, { ...properties, inline_dashboard: false }, context) + expect(clients).toHaveLength(1) + expect(clients[0].destroyed).toBe(false) + }) + + it.each([0, -1, 1.5, Number.NaN])('reports an invalid upload limit (%s)', (limit) => { + update(instance, { ...properties, max_files: limit }, context) + expect(states.error_message).toContain('positive integers') + expect(instance.triggerEvent).toHaveBeenCalledWith('error') + }) +}) + +describe('signing contract', () => { + it('accepts signed Assembly Instructions', () => { + const params = JSON.stringify({ + auth: { key: 'key', expires: 'later' }, + steps: { resize: { robot: '/image/resize' } }, + }) + expect(assemblyOptions({ params, signature: 'sha384:test' })).toEqual({ + params, + signature: 'sha384:test', + }) + }) + it.each([ + {}, + { params: '{}' }, + { params: '{"auth":{"key":"key"}}' }, + { ...properties, signature: '' }, + { params: '{"auth":{"key":"key"},"template_id":"template"}', signature: 'sha384:test' }, + ])('rejects missing or invalid signed configuration', (config) => { + expect(() => assemblyOptions(config)).toThrow() + }) +}) + +describe('legacy open action', () => { + it('opens current Uppy with existing shared keys and releases it on close', async () => { + open({}, { ...context, keys: { 'AUTH KEY': 'public-key', 'TEMPLATE ID': 'template' } }) + const client = clients[0] + expect(client.getPlugin('Dashboard').openModal).toHaveBeenCalledOnce() + const options = client.getPlugin('Transloadit').opts.assemblyOptions() + expect(JSON.parse(options.params)).toEqual({ + auth: { key: 'public-key' }, + template_id: 'template', + }) + client.emit('dashboard:modal-closed') + expect(client.destroyed).toBe(true) + }) + it('forwards completed Assemblies to the old notification URL and reports failures', async () => { + const fetch = vi.fn().mockResolvedValue({ ok: false, status: 500 }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + clients[0].emit('transloadit:complete', { assembly_id: 'a' }) + await vi.waitFor(() => + expect(context.reportDebugger).toHaveBeenCalledWith('Transloadit notification failed'), + ) + expect(fetch).toHaveBeenCalledWith( + 'https://example.com/hook', + expect.objectContaining({ body: '[{"assembly_id":"a"}]' }), + ) + clients[0].emit('dashboard:modal-closed') + vi.unstubAllGlobals() + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0a00696 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2022", + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["es2022", "dom"], + "strict": true, + "noEmit": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "skipLibCheck": true, + "types": [] + } +} diff --git a/vitest.config.mjs b/vitest.config.mjs new file mode 100644 index 0000000..4b98862 --- /dev/null +++ b/vitest.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/unit/**/*.test.ts'], + environment: 'jsdom', + coverage: { + include: ['src/**/*.ts'], + thresholds: { lines: 85, functions: 85, statements: 85, branches: 75 }, + }, + }, +}) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..a98c0e9 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3240 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@acemir/cssom@npm:^0.9.31": + version: 0.9.32 + resolution: "@acemir/cssom@npm:0.9.32" + checksum: 10c0/663a49a1e8be095d9b0d39d85d716181efe8af83ef95a49fad67963486951b81c0a91d40ba4d5a54efbac04bb42b8b4f9a4aa2cd7e513b7cd62250b9fae1ae1a + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^5.0.1": + version: 5.1.11 + resolution: "@asamuzakjp/css-color@npm:5.1.11" + dependencies: + "@asamuzakjp/generational-cache": "npm:^1.0.1" + "@csstools/css-calc": "npm:^3.2.0" + "@csstools/css-color-parser": "npm:^4.1.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + checksum: 10c0/32720bdff8daea6a8847aba6cdfae55baa3b4a2690b51d21db7f0382bbd183f3d9f2d5126df50afd889062635684b2819e47113629ee2e80c99389e75f48d060 + languageName: node + linkType: hard + +"@asamuzakjp/dom-selector@npm:^6.8.1": + version: 6.8.1 + resolution: "@asamuzakjp/dom-selector@npm:6.8.1" + dependencies: + "@asamuzakjp/nwsapi": "npm:^2.3.9" + bidi-js: "npm:^1.0.3" + css-tree: "npm:^3.1.0" + is-potential-custom-element-name: "npm:^1.0.1" + lru-cache: "npm:^11.2.6" + checksum: 10c0/635de2c3b11971c07e2d491fd2833d2499bafbab05b616f5d38041031718879c404456644f60c45e9ba4ca2423e5bb48bf3c46179b0c58a0ea68eaae8c61e85f + languageName: node + linkType: hard + +"@asamuzakjp/generational-cache@npm:^1.0.1": + version: 1.0.1 + resolution: "@asamuzakjp/generational-cache@npm:1.0.1" + checksum: 10c0/1de62de43764e13fca3b9a31b7ea9b1bf0780fe053d266e40378a19ff8c66b543e011e6a0df02d410cd59bf981126706f176cdbb938985165202c4a079fe1057 + languageName: node + linkType: hard + +"@asamuzakjp/nwsapi@npm:^2.3.9": + version: 2.3.9 + resolution: "@asamuzakjp/nwsapi@npm:2.3.9" + checksum: 10c0/869b81382e775499c96c45c6dbe0d0766a6da04bcf0abb79f5333535c4e19946851acaa43398f896e2ecc5a1de9cf3db7cf8c4b1afac1ee3d15e21584546d74d + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.11": + version: 7.29.11 + resolution: "@babel/helper-string-parser@npm:7.29.11" + checksum: 10c0/673a309056c593af89296464ff149d0478889e3f5a577add33f1d4d38c54e44d0381cf232f744965ee71534489ebaa4d5c45ba5f87e1d2ce9bf54a67d2d0d72d + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/parser@npm:^7.29.7": + version: 7.29.11 + resolution: "@babel/parser@npm:7.29.11" + dependencies: + "@babel/types": "npm:^7.29.11" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/4bf9d2e8520627c20bfefe34a0117348bf62a002fb1fdbeeee82a08ee02ff71767e079ac4e728a76da2e0db48cabf50121b3bdac0831ed4a9a6d8aa12765846d + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.5.5": + version: 7.29.10 + resolution: "@babel/runtime@npm:7.29.10" + checksum: 10c0/32f975da8e9c2cae6b33e1a477f8fe8bbff6722c982b5ce35f99affaf3a7a1b612c8272b43afa0e51d3000928d6f3ba38f9296301115db5640264907c027b0cd + languageName: node + linkType: hard + +"@babel/types@npm:^7.29.11, @babel/types@npm:^7.29.7": + version: 7.29.11 + resolution: "@babel/types@npm:7.29.11" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.11" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/d0d4edd2fbff0a8e79af1fea3c3c056de86bc0e01ed734d00062287638379933a232f087959c6eb67365c2f4fb44d57b88793cd1e87f0aad568f1c8b3b94da80 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + +"@biomejs/biome@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/biome@npm:2.4.16" + dependencies: + "@biomejs/cli-darwin-arm64": "npm:2.4.16" + "@biomejs/cli-darwin-x64": "npm:2.4.16" + "@biomejs/cli-linux-arm64": "npm:2.4.16" + "@biomejs/cli-linux-arm64-musl": "npm:2.4.16" + "@biomejs/cli-linux-x64": "npm:2.4.16" + "@biomejs/cli-linux-x64-musl": "npm:2.4.16" + "@biomejs/cli-win32-arm64": "npm:2.4.16" + "@biomejs/cli-win32-x64": "npm:2.4.16" + dependenciesMeta: + "@biomejs/cli-darwin-arm64": + optional: true + "@biomejs/cli-darwin-x64": + optional: true + "@biomejs/cli-linux-arm64": + optional: true + "@biomejs/cli-linux-arm64-musl": + optional: true + "@biomejs/cli-linux-x64": + optional: true + "@biomejs/cli-linux-x64-musl": + optional: true + "@biomejs/cli-win32-arm64": + optional: true + "@biomejs/cli-win32-x64": + optional: true + bin: + biome: bin/biome + checksum: 10c0/c52c4309daf241888b43912a43d1ba8bae7c2160751c33900088517da3e47d1428bb1c1f64f657efa5f42f5866e6400325b39fc41c3adf7cc7aa3f237f6fa286 + languageName: node + linkType: hard + +"@biomejs/cli-darwin-arm64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-darwin-arm64@npm:2.4.16" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-darwin-x64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-darwin-x64@npm:2.4.16" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64-musl@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-linux-arm64-musl@npm:2.4.16" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-linux-arm64@npm:2.4.16" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64-musl@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-linux-x64-musl@npm:2.4.16" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-linux-x64@npm:2.4.16" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-win32-arm64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-win32-arm64@npm:2.4.16" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-win32-x64@npm:2.4.16": + version: 2.4.16 + resolution: "@biomejs/cli-win32-x64@npm:2.4.16" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@bramus/specificity@npm:^2.4.2": + version: 2.4.3 + resolution: "@bramus/specificity@npm:2.4.3" + dependencies: + css-tree: "npm:^3.2.1" + bin: + specificity: bin/cli.js + checksum: 10c0/1d4e7b3fd90a7c17b85059090551db7be9f3097481ce291303c344342328956616f4eb7822d5a4b7bab413e5286f66f3089eb0f86502877456215c45b23d277d + languageName: node + linkType: hard + +"@changesets/apply-release-plan@npm:^7.1.1": + version: 7.1.1 + resolution: "@changesets/apply-release-plan@npm:7.1.1" + dependencies: + "@changesets/config": "npm:^3.1.4" + "@changesets/get-version-range-type": "npm:^0.4.0" + "@changesets/git": "npm:^3.0.4" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + detect-indent: "npm:^6.0.0" + fs-extra: "npm:^7.0.1" + lodash.startcase: "npm:^4.4.0" + outdent: "npm:^0.5.0" + prettier: "npm:^2.7.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + checksum: 10c0/27de184e74e8e48b43fca1f73e7c7a2887b0cdacfe7ba9c09cdc4547dff0de1587bed5fe2d5ec0a3754fa422f6b8a528e0ac452c22ac7a6ae5211f5ac089bfb2 + languageName: node + linkType: hard + +"@changesets/assemble-release-plan@npm:^6.0.10": + version: 6.0.10 + resolution: "@changesets/assemble-release-plan@npm:6.0.10" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + semver: "npm:^7.5.3" + checksum: 10c0/a0ea336a5f19f8d0a97b684983bcd9c3bb8d6881b7b6abd5b482b301795ae4600924c188982f5f98dc48ac88e94a063b66ab72659041eb2623ade3e35f05d555 + languageName: node + linkType: hard + +"@changesets/changelog-git@npm:^0.2.1": + version: 0.2.1 + resolution: "@changesets/changelog-git@npm:0.2.1" + dependencies: + "@changesets/types": "npm:^6.1.0" + checksum: 10c0/6a6fb315ffb2266fcb8f32ae9a60ccdb5436e52350a2f53beacf9822d3355f9052aba5001a718e12af472b4a8fabd69b408d0b11c02ac909ba7a183d27a9f7fd + languageName: node + linkType: hard + +"@changesets/cli@npm:^2.31.0": + version: 2.31.1 + resolution: "@changesets/cli@npm:2.31.1" + dependencies: + "@changesets/apply-release-plan": "npm:^7.1.1" + "@changesets/assemble-release-plan": "npm:^6.0.10" + "@changesets/changelog-git": "npm:^0.2.1" + "@changesets/config": "npm:^3.1.4" + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/get-release-plan": "npm:^4.0.16" + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.7" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@changesets/write": "npm:^0.4.0" + "@inquirer/external-editor": "npm:^1.0.2" + "@manypkg/get-packages": "npm:^1.1.3" + ansi-colors: "npm:^4.1.3" + enquirer: "npm:^2.4.1" + fs-extra: "npm:^7.0.1" + mri: "npm:^1.2.0" + package-manager-detector: "npm:^0.2.0" + picocolors: "npm:^1.1.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + spawndamnit: "npm:^3.0.1" + term-size: "npm:^2.1.0" + bin: + changeset: bin.js + checksum: 10c0/375789e85def0bb303ada45d5085c92765010e6b88b8252eb459346ac8a94eba098c9cb8ad416241757796a8828c417fc9e0a23011bd3a74ce33eef013d8311a + languageName: node + linkType: hard + +"@changesets/config@npm:^3.1.4": + version: 3.1.4 + resolution: "@changesets/config@npm:3.1.4" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + micromatch: "npm:^4.0.8" + checksum: 10c0/1c0e7975aa719e2c87dfda3f5a1eb81b9f4852cdfb5b5c9d181fa2f8f485e92370b3bdfdb6f666432207dd75a3f79fa8fffe7847d48fb11308acb5ecf327bc12 + languageName: node + linkType: hard + +"@changesets/errors@npm:^0.2.0": + version: 0.2.0 + resolution: "@changesets/errors@npm:0.2.0" + dependencies: + extendable-error: "npm:^0.1.5" + checksum: 10c0/f2757c752ab04e9733b0dfd7903f1caf873f9e603794c4d9ea2294af4f937c73d07273c24be864ad0c30b6a98424360d5b96a6eab14f97f3cf2cbfd3763b95c1 + languageName: node + linkType: hard + +"@changesets/get-dependents-graph@npm:^2.1.4": + version: 2.1.4 + resolution: "@changesets/get-dependents-graph@npm:2.1.4" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + picocolors: "npm:^1.1.0" + semver: "npm:^7.5.3" + checksum: 10c0/37b12ba42f16c458d0b574bcafa0247ff2b9a218686a64c86fc75bccc9ba3982f9c27206542941cf3a0563d9b199f40a830682b45e9fd902536de91344cbd0a2 + languageName: node + linkType: hard + +"@changesets/get-release-plan@npm:^4.0.16": + version: 4.0.16 + resolution: "@changesets/get-release-plan@npm:4.0.16" + dependencies: + "@changesets/assemble-release-plan": "npm:^6.0.10" + "@changesets/config": "npm:^3.1.4" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.7" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10c0/4be4553e13fe331f6d5b2ed98fece21c8d2b38c04a0543f726a0398b7538ef8fd073d712c35ae4540ed4fc6f84f08de6335318bc09dd562b189fb6968d049e95 + languageName: node + linkType: hard + +"@changesets/get-version-range-type@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/get-version-range-type@npm:0.4.0" + checksum: 10c0/e466208c8383489a383f37958d8b5b9aed38539f9287b47fe155a2e8855973f6960fb1724a1ee33b11580d65e1011059045ee654e8ef51e4783017d8989c9d3f + languageName: node + linkType: hard + +"@changesets/git@npm:^3.0.4": + version: 3.0.4 + resolution: "@changesets/git@npm:3.0.4" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@manypkg/get-packages": "npm:^1.1.3" + is-subdir: "npm:^1.1.1" + micromatch: "npm:^4.0.8" + spawndamnit: "npm:^3.0.1" + checksum: 10c0/4abbdc1dec6ddc50b6ad927d9eba4f23acd775fdff615415813099befb0cecd1b0f56ceea5e18a5a3cbbb919d68179366074b02a954fbf4016501e5fd125d2b5 + languageName: node + linkType: hard + +"@changesets/logger@npm:^0.1.1": + version: 0.1.1 + resolution: "@changesets/logger@npm:0.1.1" + dependencies: + picocolors: "npm:^1.1.0" + checksum: 10c0/a0933b5bd4d99e10730b22612dc1bdfd25b8804c5b48f8cada050bf5c7a89b2ae9a61687f846a5e9e5d379a95b59fef795c8d5d91e49a251f8da2be76133f83f + languageName: node + linkType: hard + +"@changesets/parse@npm:^0.4.3": + version: 0.4.3 + resolution: "@changesets/parse@npm:0.4.3" + dependencies: + "@changesets/types": "npm:^6.1.0" + js-yaml: "npm:^4.1.1" + checksum: 10c0/4d8488eaf224974ae335fec964dc1dc486abcfa9f96856cf4267c2765b02ed6af1778375ec03d38252ebab9e191aa4a11c5f37a6ad42e907e08290fed2b9690c + languageName: node + linkType: hard + +"@changesets/pre@npm:^2.0.2": + version: 2.0.2 + resolution: "@changesets/pre@npm:2.0.2" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + checksum: 10c0/0af9396d84c47a88d79b757e9db4e3579b6620260f92c243b8349e7fcefca3c2652583f6d215c13115bed5d5cdc30c975f307fd6acbb89d205b1ba2ae403b918 + languageName: node + linkType: hard + +"@changesets/read@npm:^0.6.7": + version: 0.6.7 + resolution: "@changesets/read@npm:0.6.7" + dependencies: + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/parse": "npm:^0.4.3" + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + p-filter: "npm:^2.1.0" + picocolors: "npm:^1.1.0" + checksum: 10c0/eebda5f5cea8684b9cb470e74cd5e67043a62ca54452ac88bb1a998bebeee1a2e3a642dc76818155a145863551c65f10f9c4ff85378b0419179fc60049edbbc6 + languageName: node + linkType: hard + +"@changesets/should-skip-package@npm:^0.1.2": + version: 0.1.2 + resolution: "@changesets/should-skip-package@npm:0.1.2" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10c0/484e339e7d6e6950e12bff4eda6e8eccb077c0fbb1f09dd95d2ae948b715226a838c71eaf50cd2d7e0e631ce3bfb1ca93ac752436e6feae5b87aece2e917b440 + languageName: node + linkType: hard + +"@changesets/types@npm:^4.0.1": + version: 4.1.0 + resolution: "@changesets/types@npm:4.1.0" + checksum: 10c0/a372ad21f6a1e0d4ce6c19573c1ca269eef1ad53c26751ad9515a24f003e7c49dcd859dbb1fedb6badaf7be956c1559e8798304039e0ec0da2d9a68583f13464 + languageName: node + linkType: hard + +"@changesets/types@npm:^6.1.0": + version: 6.1.0 + resolution: "@changesets/types@npm:6.1.0" + checksum: 10c0/b4cea3a4465d1eaf0bbd7be1e404aca5a055a61d4cc72aadcb73bbbda1670b4022736b8d3052616cbf1f451afa0637545d077697f4b923236539af9cd5abce6c + languageName: node + linkType: hard + +"@changesets/write@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/write@npm:0.4.0" + dependencies: + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + human-id: "npm:^4.1.1" + prettier: "npm:^2.7.1" + checksum: 10c0/311f4d0e536d1b5f2d3f9053537d62b2d4cdbd51e1d2767807ac9d1e0f380367f915d2ad370e5c73902d5a54bffd282d53fff5418c8ad31df51751d652bea826 + languageName: node + linkType: hard + +"@csstools/color-helpers@npm:^6.1.2": + version: 6.1.2 + resolution: "@csstools/color-helpers@npm:6.1.2" + checksum: 10c0/58cc626cc78ccfec1be2c51c3e19bf83fd7ed9166d5c71be39bc306b9c8c9791cd39606e25838c554c930b952b479d32d08890100dc5d60d039d8f45d8127335 + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^3.2.0, @csstools/css-calc@npm:^3.4.3": + version: 3.4.3 + resolution: "@csstools/css-calc@npm:3.4.3" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.2 + "@csstools/css-tokenizer": ^4.0.2 + checksum: 10c0/2138888cd2f2736eba4eed09a23f2666b343ab57a6dcb55fcaf8d0cfc768ab1b01ad8f35ccca3d043f30fbf862f9beac2ba4a68c3b7a086835957f99ca50939d + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^4.1.0": + version: 4.2.6 + resolution: "@csstools/css-color-parser@npm:4.2.6" + dependencies: + "@csstools/color-helpers": "npm:^6.1.2" + "@csstools/css-calc": "npm:^3.4.3" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.2 + "@csstools/css-tokenizer": ^4.0.2 + checksum: 10c0/00ebb71b356095173c60b41df799991b59ba543066cb44cd68a300785f142723d045179ac1d8c06b8811eb78b1b1f8545d8f31f814d86f23242aa94fd3d1c8aa + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^4.0.0": + version: 4.0.2 + resolution: "@csstools/css-parser-algorithms@npm:4.0.2" + peerDependencies: + "@csstools/css-tokenizer": ^4.0.2 + checksum: 10c0/ecd411e081ad522fb2e94d7b48d7f8e20cf5ef5f2017addecb80746f7403d289b4e403aee7422330a88f4193942e10bedb960326a7c0567528fc2273b0be5db7 + languageName: node + linkType: hard + +"@csstools/css-syntax-patches-for-csstree@npm:^1.0.28": + version: 1.1.15 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.15" + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + checksum: 10c0/81fa30efa74821793a4c683f79d9ac20b29f426660362ac8eb61f2a5c364a25850258cc5578739f8448532c99f49c6f89b9ae03f386e2ec384cafe43da0a745e + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^4.0.0": + version: 4.0.2 + resolution: "@csstools/css-tokenizer@npm:4.0.2" + checksum: 10c0/748b42450064cca6a54d2bb8a719cb765c6433b8988ed8855f39c9814b8ef30c4daab235ffee9dacb0388916d6e250ec6bee454e8353d06161228e129e4272ae + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/aix-ppc64@npm:0.28.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm64@npm:0.28.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm@npm:0.28.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-x64@npm:0.28.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-arm64@npm:0.28.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-x64@npm:0.28.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-arm64@npm:0.28.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-x64@npm:0.28.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm64@npm:0.28.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm@npm:0.28.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ia32@npm:0.28.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-loong64@npm:0.28.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-mips64el@npm:0.28.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ppc64@npm:0.28.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-riscv64@npm:0.28.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-s390x@npm:0.28.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-x64@npm:0.28.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-arm64@npm:0.28.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-x64@npm:0.28.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-arm64@npm:0.28.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-x64@npm:0.28.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openharmony-arm64@npm:0.28.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/sunos-x64@npm:0.28.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-arm64@npm:0.28.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-ia32@npm:0.28.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-x64@npm:0.28.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.6.0": + version: 1.16.0 + resolution: "@exodus/bytes@npm:1.16.0" + peerDependencies: + "@noble/hashes": ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + "@noble/hashes": + optional: true + checksum: 10c0/cf0c2f790669cccf3a1fd6d9fb71defdf90d5ccabca6ea265829288291cad18f19fee115932e9a44e16bba94d1f093f92a1cdb197e88b60a5b7a2f6a923bf01d + languageName: node + linkType: hard + +"@inquirer/external-editor@npm:^1.0.2": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.31": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@manypkg/find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "@manypkg/find-root@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@types/node": "npm:^12.7.1" + find-up: "npm:^4.1.0" + fs-extra: "npm:^8.1.0" + checksum: 10c0/0ee907698e6c73d6f1821ff630f3fec6dcf38260817c8752fec8991ac38b95ba431ab11c2773ddf9beb33d0e057f1122b00e8ffc9b8411b3fd24151413626fa6 + languageName: node + linkType: hard + +"@manypkg/get-packages@npm:^1.1.3": + version: 1.1.3 + resolution: "@manypkg/get-packages@npm:1.1.3" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@changesets/types": "npm:^4.0.1" + "@manypkg/find-root": "npm:^1.1.0" + fs-extra: "npm:^8.1.0" + globby: "npm:^11.0.0" + read-yaml-file: "npm:^1.1.0" + checksum: 10c0/f05907d1174ae28861eaa06d0efdc144f773d9a4b8b65e1e7cdc01eb93361d335351b4a336e05c6aac02661be39e8809a3f7ad28bc67b6b338071434ab442130 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.155.0": + version: 0.155.0 + resolution: "@oxc-project/types@npm:0.155.0" + checksum: 10c0/07227fb2c2e03b07453129f2407b49b165eadaa679a34dad68e0b0526420506cea223431807983413f6968053de873c13b05d3978a9a43cabb3999ca97ba2ec5 + languageName: node + linkType: hard + +"@playwright/test@npm:1.64.1": + version: 1.64.1 + resolution: "@playwright/test@npm:1.64.1" + dependencies: + playwright: "npm:1.64.1" + bin: + playwright: cli.js + checksum: 10c0/0a1a853d76d6c532cafaf0366a5448584b766737ebd39bbd23dc0580f3d3b6ef4c833082af47a792e7fc2501f444debdfaad37a87a76cc5b195f3803fc80ea3c + languageName: node + linkType: hard + +"@rolldown/binding-android-arm-eabi@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.15" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-android-arm64@npm:1.2.15" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.15" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.15" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.15" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.15" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.15" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.15" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.15" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.15" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.15" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.15" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.15" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.15" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.2.15": + version: 1.2.15 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.15" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@transloadit/prettier-bytes@npm:^2.0.0": + version: 2.0.0 + resolution: "@transloadit/prettier-bytes@npm:2.0.0" + checksum: 10c0/34aaf05cb4c85de549ef5e894460275471d5b4c7568270a262634642f056919d7b4dc44ba49f2e63400a2f4e0e1434e264eb1c03de3927b523ef0c648b51f9b9 + languageName: node + linkType: hard + +"@transloadit/types@npm:^4.4.0": + version: 4.5.0 + resolution: "@transloadit/types@npm:4.5.0" + checksum: 10c0/d02f1676c74a594b8b735273e4f19c62c770ca71f28a6e5cc2de9724e14084a9272d7d6222af0cb39066952608a2cd0e245d8c27f9d0c38dd0b31fcb2452d0f2 + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.0": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + +"@types/node@npm:^12.7.1": + version: 12.20.55 + resolution: "@types/node@npm:12.20.55" + checksum: 10c0/3b190bb0410047d489c49bbaab592d2e6630de6a50f00ba3d7d513d59401d279972a8f5a598b5bb8ddc1702f8a2f4ec57a65d93852f9c329639738e7053637d1 + languageName: node + linkType: hard + +"@types/node@npm:^22": + version: 22.20.5 + resolution: "@types/node@npm:22.20.5" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/0cf61a89c6a69917ab49f976a6bb11a29e3b348a6ae19f5827ceb91d3ade781faab58dcf3b8fa625f975f5d7536b9166ff94e8250c29272c667164f77c28fa88 + languageName: node + linkType: hard + +"@uppy/core@npm:6.3.0": + version: 6.3.0 + resolution: "@uppy/core@npm:6.3.0" + dependencies: + "@transloadit/prettier-bytes": "npm:^2.0.0" + classnames: "npm:^2.5.1" + lodash: "npm:^4.18.1" + mime-match: "npm:^1.0.2" + namespace-emitter: "npm:^2.0.1" + nanoid: "npm:^6.0.1" + p-queue: "npm:^9.3.3" + p-retry: "npm:^8.0.0" + preact: "npm:^10.29.2" + checksum: 10c0/c82dd86cb1c1db3360239e777c88843fe061b08d11e52c88e08da66493d73ccb75046043211c9810a5daa32a8c561f7bef86127e747f785cb9ea0ede2e39034c + languageName: node + linkType: hard + +"@uppy/dashboard@npm:6.0.2": + version: 6.0.2 + resolution: "@uppy/dashboard@npm:6.0.2" + dependencies: + "@transloadit/prettier-bytes": "npm:^2.0.0" + "@uppy/thumbnail-generator": "npm:^6.0.1" + classnames: "npm:^2.5.1" + lodash: "npm:^4.18.1" + nanoid: "npm:^6.0.1" + preact: "npm:^10.29.2" + shallow-equal: "npm:^3.1.0" + peerDependencies: + "@uppy/core": ^6.3.0 + checksum: 10c0/454c9d4c3f7f83d238465cebbf62016dc4486c25fbd1813dd9206d44207f37576c31a97635275cf4ea720857cbb7b79308ec63b84648d4465ce8a2e5950f8999 + languageName: node + linkType: hard + +"@uppy/thumbnail-generator@npm:^6.0.1": + version: 6.0.1 + resolution: "@uppy/thumbnail-generator@npm:6.0.1" + dependencies: + exifr: "npm:^7.1.3" + peerDependencies: + "@uppy/core": ^6.2.0 + checksum: 10c0/7fdd69b76020ca9952f7d5983193a8bbdc5de24ca9672681262fd27264cccfe8267fd8f2b9d6962b4144c2af9a8f7351bef6ce2d5a4f94b170822ea12da40df2 + languageName: node + linkType: hard + +"@uppy/transloadit@npm:6.0.1": + version: 6.0.1 + resolution: "@uppy/transloadit@npm:6.0.1" + dependencies: + "@transloadit/types": "npm:^4.4.0" + "@uppy/tus": "npm:^6.0.0" + component-emitter: "npm:^2.0.0" + peerDependencies: + "@uppy/core": ^6.2.0 + checksum: 10c0/4e156615099b55c8188bffc918177183c96097dd4633e4f9b0bdef62f829c9a05c1d18167f2ab8b5090259d05aed6764cffb1aec96278f1f814c57673ec6b8c7 + languageName: node + linkType: hard + +"@uppy/tus@npm:^6.0.0": + version: 6.0.0 + resolution: "@uppy/tus@npm:6.0.0" + dependencies: + tus-js-client: "npm:^4.3.1" + peerDependencies: + "@uppy/core": ^6.0.0 + checksum: 10c0/5a0bcb22506840bfac740c924fd7099e0d811325106a44182aef528b4fc1b3f29ffe8130ed47af6a0b3fe88429f0c42e8db0eaaaa3e6e38abcfce80bee07ae30 + languageName: node + linkType: hard + +"@vitest/coverage-v8@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/coverage-v8@npm:4.1.11" + dependencies: + "@bcoe/v8-coverage": "npm:^1.0.2" + "@vitest/utils": "npm:4.1.11" + ast-v8-to-istanbul: "npm:^1.0.0" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.2" + obug: "npm:^2.1.1" + std-env: "npm:^4.0.0-rc.1" + tinyrainbow: "npm:^3.1.0" + peerDependencies: + "@vitest/browser": 4.1.11 + vitest: 4.1.11 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/91127fd40f445b506cc661c54e26defd75d970fa1983cb83442856dd7f295542f0378e0bca847036a7a5be871b3b17c27167d21f277e47368cf7409eafaa1b40 + languageName: node + linkType: hard + +"@vitest/expect@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/expect@npm:4.1.11" + dependencies: + "@standard-schema/spec": "npm:^1.1.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/0aa5e0973aca93a58cbdc3041c6bfed5897e976124965203b96a23b811f4ca403590c7eb15802c8d8366ec27a1db0682451e8a91c4d06617636de262baf86e4b + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/mocker@npm:4.1.11" + dependencies: + "@vitest/spy": "npm:4.1.11" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/3111ea34bd5046f6c70bbd67cf8b89608ee8c41cb27ab2bd61d42f4b68810e9ea16a9a757a71bc254c105f73b407d00ebb6bab1ab0f7f5cdcc9d7d16602a3933 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/pretty-format@npm:4.1.11" + dependencies: + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/ad32525c73807c0b72f38dc29bc51fd5a17879dc650f37995a9c5adbb8526e15f787691f76aa8768448ec7ed5bf5ba2b12329eac8fda1428b4d6b8c036390f71 + languageName: node + linkType: hard + +"@vitest/runner@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/runner@npm:4.1.11" + dependencies: + "@vitest/utils": "npm:4.1.11" + pathe: "npm:^2.0.3" + checksum: 10c0/3c782b055e9e688e1785f7c8937bd1669bad1b0e5758cb8b844f918f30a324b1d21e174d46c941ce80ebf37f2b89b55b6d619a675025914a1ece6377b95909e2 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/snapshot@npm:4.1.11" + dependencies: + "@vitest/pretty-format": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/35d82a7c2a3e4b57529c30387d568d9106b6bf960189214e5d8cb1f5288cb042305c2e5c7b0b2f8cb56a2c814973de0310bc56d72deb79427bea272b6224190c + languageName: node + linkType: hard + +"@vitest/spy@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/spy@npm:4.1.11" + checksum: 10c0/06c68247a8efd21006abe7532fee17f30ba83c8cda3e0b952ef89e278d58058f4b1de69b6bbaa2ed614c568bf4f5769fcc76be42802dedf2bcdd9c0aae601740 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/utils@npm:4.1.11" + dependencies: + "@vitest/pretty-format": "npm:4.1.11" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/a2c1ddc64333458c3e031465c1ee0440a7660fd1b298c54ccbf865ef1e5cf3ebdd6c5c75a5ea235d8a672498b394e7121f992b096f021f45014ab25e9abd1b52 + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"ast-v8-to-istanbul@npm:^1.0.0": + version: 1.0.7 + resolution: "ast-v8-to-istanbul@npm:1.0.7" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.31" + estree-walker: "npm:^3.0.3" + js-tokens: "npm:^10.0.0" + checksum: 10c0/c6bbfbf8b8adc8d793f8d10fbf9bcb25b0c65f2faa73ec1e41d2937a9c18327167b9395134943a4814dd3d095d2453b2f0f71a5d33aecd808ac4725c23e69918 + languageName: node + linkType: hard + +"better-path-resolve@npm:1.0.0": + version: 1.0.0 + resolution: "better-path-resolve@npm:1.0.0" + dependencies: + is-windows: "npm:^1.0.0" + checksum: 10c0/7335130729d59a14b8e4753fea180ca84e287cccc20cb5f2438a95667abc5810327c414eee7b3c79ed1b5a348a40284ea872958f50caba69432c40405eb0acce + languageName: node + linkType: hard + +"bidi-js@npm:^1.0.3": + version: 1.1.0 + resolution: "bidi-js@npm:1.1.0" + dependencies: + require-from-string: "npm:^2.0.2" + checksum: 10c0/b608addec6bd6e5d1c0cc7b6116002d589c6e666966503aca2aa7b2f61717ecaaeb0ac1a16c1374d61ee757de4f2f24017312a70781f5c4b127ffab54a2f7187 + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"buffer-from@npm:^1.1.2": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"chai@npm:^6.2.2": + version: 6.3.0 + resolution: "chai@npm:6.3.0" + checksum: 10c0/fd1bf7fc41295ba1eba18a44d68e204b3cb11b79c9ad51fd071de3ee85323c20d94afc07a386f1f5ff44a5da4a116614d419fc44a2d459e4b5b574aef9af4431 + languageName: node + linkType: hard + +"chardet@npm:^2.1.1": + version: 2.2.0 + resolution: "chardet@npm:2.2.0" + checksum: 10c0/8d43a1dd3ce535aa070d04139fae62f879b4388394eb1d857364ce416675a1f0f63974fba8208e9cd11b49e1a6da3e65ea6393d0fc654a8c4217c0d74c16b1b4 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"classnames@npm:^2.5.1": + version: 2.5.1 + resolution: "classnames@npm:2.5.1" + checksum: 10c0/afff4f77e62cea2d79c39962980bf316bacb0d7c49e13a21adaadb9221e1c6b9d3cdb829d8bb1b23c406f4e740507f37e1dcf506f7e3b7113d17c5bab787aa69 + languageName: node + linkType: hard + +"combine-errors@npm:^3.0.3": + version: 3.0.3 + resolution: "combine-errors@npm:3.0.3" + dependencies: + custom-error-instance: "npm:2.1.1" + lodash.uniqby: "npm:4.5.0" + checksum: 10c0/6e7c04102596dfc43ddb252c8390e869e358fb6df3b027567b33f5d998fdb2ad84573ce30a3861feb84fdee1fc3da3c465d68e23f7ab39f98bbe846ca9f70c6d + languageName: node + linkType: hard + +"component-emitter@npm:^2.0.0": + version: 2.0.0 + resolution: "component-emitter@npm:2.0.0" + checksum: 10c0/65dfaf787ea49eb48f0ffec766bda7ec67e8dbeb3b406f08724dcae842e0aa274731fcccb9280b77d2b41693061731a9080b60d276020246a146544cd9900b83 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.5": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"css-tree@npm:^3.1.0, css-tree@npm:^3.2.1": + version: 3.2.1 + resolution: "css-tree@npm:3.2.1" + dependencies: + mdn-data: "npm:2.27.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/1f65e9ccaa56112a4706d6f003dd43d777f0dbcf848e66fd320f823192533581f8dd58daa906cb80622658332d50284d6be13b87a6ab4556cbbfe9ef535bbf7e + languageName: node + linkType: hard + +"cssstyle@npm:^6.0.1": + version: 6.2.0 + resolution: "cssstyle@npm:6.2.0" + dependencies: + "@asamuzakjp/css-color": "npm:^5.0.1" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.28" + css-tree: "npm:^3.1.0" + lru-cache: "npm:^11.2.6" + checksum: 10c0/d5e61973a8c1b4fb9727edddfb9f2677c9a91b1db63787fc0c8bed639a227a97fcf930e5aabc3c64c8280d63169c632015a39da4a84083d1731c949437d0a2a2 + languageName: node + linkType: hard + +"custom-error-instance@npm:2.1.1": + version: 2.1.1 + resolution: "custom-error-instance@npm:2.1.1" + checksum: 10c0/c4f68550ae88426c49810846c62651438f04a10462c4d8667c089fb9264bcf1723c9b188e3f1d7791d727b0e5aa48ed1de0415eef50d43ea078517844296b85e + languageName: node + linkType: hard + +"data-urls@npm:^7.0.0": + version: 7.0.0 + resolution: "data-urls@npm:7.0.0" + dependencies: + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.0" + checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"decimal.js@npm:^10.6.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa + languageName: node + linkType: hard + +"detect-indent@npm:^6.0.0": + version: 6.1.0 + resolution: "detect-indent@npm:6.1.0" + checksum: 10c0/dd83cdeda9af219cf77f5e9a0dc31d828c045337386cfb55ce04fad94ba872ee7957336834154f7647b89b899c3c7acc977c57a79b7c776b506240993f97acc7 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"enquirer@npm:^2.4.1": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: "npm:^4.1.1" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/43850479d7a51d36a9c924b518dcdc6373b5a8ae3401097d336b7b7e258324749d0ad37a1fcaa5706f04799baa05585cd7af19ebdf7667673e7694435fcea918 + languageName: node + linkType: hard + +"entities@npm:^8.0.0": + version: 8.1.0 + resolution: "entities@npm:8.1.0" + checksum: 10c0/da0bf22912c19d2a4707783e1b905d3f6a977e93e0655a8d2c526455eb3996f668f9f2e4c2a07af8d6927d232c41b6e929a7044df0236c7bf6e9c38e070e3719 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"es-module-lexer@npm:^2.0.0": + version: 2.3.2 + resolution: "es-module-lexer@npm:2.3.2" + checksum: 10c0/5e7389424c43478439f12f9a6aca1750f6f99afa384fc3de329f4a45f152ab156671055008adaafc74960877abf8cc338aeebf6bed8c21297b146fd6eb7a22f8 + languageName: node + linkType: hard + +"esbuild@npm:0.28.2": + version: 0.28.2 + resolution: "esbuild@npm:0.28.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.2" + "@esbuild/android-arm": "npm:0.28.2" + "@esbuild/android-arm64": "npm:0.28.2" + "@esbuild/android-x64": "npm:0.28.2" + "@esbuild/darwin-arm64": "npm:0.28.2" + "@esbuild/darwin-x64": "npm:0.28.2" + "@esbuild/freebsd-arm64": "npm:0.28.2" + "@esbuild/freebsd-x64": "npm:0.28.2" + "@esbuild/linux-arm": "npm:0.28.2" + "@esbuild/linux-arm64": "npm:0.28.2" + "@esbuild/linux-ia32": "npm:0.28.2" + "@esbuild/linux-loong64": "npm:0.28.2" + "@esbuild/linux-mips64el": "npm:0.28.2" + "@esbuild/linux-ppc64": "npm:0.28.2" + "@esbuild/linux-riscv64": "npm:0.28.2" + "@esbuild/linux-s390x": "npm:0.28.2" + "@esbuild/linux-x64": "npm:0.28.2" + "@esbuild/netbsd-arm64": "npm:0.28.2" + "@esbuild/netbsd-x64": "npm:0.28.2" + "@esbuild/openbsd-arm64": "npm:0.28.2" + "@esbuild/openbsd-x64": "npm:0.28.2" + "@esbuild/openharmony-arm64": "npm:0.28.2" + "@esbuild/sunos-x64": "npm:0.28.2" + "@esbuild/win32-arm64": "npm:0.28.2" + "@esbuild/win32-ia32": "npm:0.28.2" + "@esbuild/win32-x64": "npm:0.28.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/9b19edb63bd7780fd2e8e65a1394a0e8a05a17d697f73f0647ffe3c134709d8dbe744ab3fd57b35c9470db268cae7678fafd3dcd3ce1f34fc590568c2433f5d2 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"eventemitter3@npm:^5.0.4": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10c0/575b8cac8d709e1473da46f8f15ef311b57ff7609445a7c71af5cd42598583eee6f098fa7a593e30f27e94b8865642baa0689e8fa97c016f742abdb3b1bf6d9a + languageName: node + linkType: hard + +"exifr@npm:^7.1.3": + version: 7.1.3 + resolution: "exifr@npm:7.1.3" + checksum: 10c0/296d66941a402f2abc42c622240c8fe943f6f7ff0526ec7aab5fe38e190cca41dadb1d5c4015ebb302ded346429524ed984b93b99815c55b3d9cc00237cee82a + languageName: node + linkType: hard + +"expect-type@npm:^1.3.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"extendable-error@npm:^0.1.5": + version: 0.1.7 + resolution: "extendable-error@npm:0.1.7" + checksum: 10c0/c46648b7682448428f81b157cbfe480170fd96359c55db477a839ddeaa34905a18cba0b989bafe5e83f93c2491a3fcc7cc536063ea326ba9d72e9c6e2fe736a7 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.20.3 + resolution: "fastq@npm:1.20.3" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/ad055a1b50f7ec9142d2e13699ad6c4d1fa9fea1d947fff89de1b5ea3825223e9a626c359b3a0f21aefbe093cc2a9e92a91e068f982a2b176af16b9ea430ffc5 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"fs-extra@npm:^7.0.1": + version: 7.0.1 + resolution: "fs-extra@npm:7.0.1" + dependencies: + graceful-fs: "npm:^4.1.2" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/1943bb2150007e3739921b8d13d4109abdc3cc481e53b97b7ea7f77eda1c3c642e27ae49eac3af074e3496ea02fde30f411ef410c760c70a38b92e656e5da784 + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"globby@npm:^11.0.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^6.0.0": + version: 6.0.0 + resolution: "html-encoding-sniffer@npm:6.0.0" + dependencies: + "@exodus/bytes": "npm:^1.6.0" + checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"human-id@npm:^4.1.1": + version: 4.2.2 + resolution: "human-id@npm:4.2.2" + bin: + human-id: dist/cli.js + checksum: 10c0/7bf51bf2cec8c26129d3f6ea068b18eca702130641afe5c475cb92270015e403be5bdd35f53b2da38a058a3c1c2632c2d0576c026ad78dc33d30af53eb7d426d + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.0": + version: 0.7.3 + resolution: "iconv-lite@npm:0.7.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/be2fd2414f7e94be3a63063fa0ad5919c16bc7b6e00c77eea0765ced6db405739f38dd51016583058fba25cc1d0106ba30b83fbb7a7e32f824d4db76f23d8d33 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-network-error@npm:^1.3.0": + version: 1.3.2 + resolution: "is-network-error@npm:1.3.2" + checksum: 10c0/37edc576497b21d022754b49203358ee80fdac4a11a71a09687f38ad789ec437dd930c223301df39f1c920566692b31345e0108ae720996e592cab3879eca74f + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-potential-custom-element-name@npm:^1.0.1": + version: 1.0.1 + resolution: "is-potential-custom-element-name@npm:1.0.1" + checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-subdir@npm:^1.1.1": + version: 1.2.0 + resolution: "is-subdir@npm:1.2.0" + dependencies: + better-path-resolve: "npm:1.0.0" + checksum: 10c0/03a03ee2ee6578ce589b1cfaf00e65c86b20fd1b82c1660625557c535439a7477cda77e20c62cda6d4c99e7fd908b4619355ae2d989f4a524a35350a44353032 + languageName: node + linkType: hard + +"is-windows@npm:^1.0.0": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10c0/b32f418ab3385604a66f1b7a3ce39d25e8881dee0bd30816dc8344ef6ff9df473a732bcc1ec4e84fe99b2f229ae474f7133e8e93f9241686cfcf7eebe53ba7a5 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + +"js-base64@npm:^3.7.2": + version: 3.9.5 + resolution: "js-base64@npm:3.9.5" + checksum: 10c0/3cfd1db27213bdc32eb7ae8cc70e8d91ed8d3486de2520846e8fe6fbc40a8932beb66549e3801d9502b9bd2d36d43a579787b47fe650807dc49a8f85c2cc3f25 + languageName: node + linkType: hard + +"js-tokens@npm:^10.0.0": + version: 10.0.0 + resolution: "js-tokens@npm:10.0.0" + checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64 + languageName: node + linkType: hard + +"js-yaml@npm:^3.6.1": + version: 3.15.2 + resolution: "js-yaml@npm:3.15.2" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/e341df211dece9d4b784849647ad7568b5b6a58a9ee58ead750482316d83276387343649fe4b71522705dc6c76acf97718588f23dc19443833ef3e75033af967 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.1": + version: 4.3.2 + resolution: "js-yaml@npm:4.3.2" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/dedd34c2e8fef1d504687f1bc94ed0ee1311a1f78770fa2800352c6d59c635ccf555009d74e9afe529ae62199da2b1ccef30e53dc84dcd8d2d8187c22574bc97 + languageName: node + linkType: hard + +"jsdom@npm:^28.1.0": + version: 28.1.0 + resolution: "jsdom@npm:28.1.0" + dependencies: + "@acemir/cssom": "npm:^0.9.31" + "@asamuzakjp/dom-selector": "npm:^6.8.1" + "@bramus/specificity": "npm:^2.4.2" + "@exodus/bytes": "npm:^1.11.0" + cssstyle: "npm:^6.0.1" + data-urls: "npm:^7.0.0" + decimal.js: "npm:^10.6.0" + html-encoding-sniffer: "npm:^6.0.0" + http-proxy-agent: "npm:^7.0.2" + https-proxy-agent: "npm:^7.0.6" + is-potential-custom-element-name: "npm:^1.0.1" + parse5: "npm:^8.0.0" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^6.0.0" + undici: "npm:^7.21.0" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^8.0.1" + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.0" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/341ecb4005be2dab3247dacc349a20285d7991b5cee3382301fcd69a4294b705b4147e7d9ae1ddfab466ba4b3aace97ded4f7b070de285262221cb2782965b25 + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"lodash._baseiteratee@npm:~4.7.0": + version: 4.7.0 + resolution: "lodash._baseiteratee@npm:4.7.0" + dependencies: + lodash._stringtopath: "npm:~4.8.0" + checksum: 10c0/67f80e6878444e44e3cc6e779941c78da2bfa02413e305b5bfbe612e6ddedbbb05bda025e673014d734b79d69db911c7cfaf25dbc05abc56356eccc90e1e59e3 + languageName: node + linkType: hard + +"lodash._basetostring@npm:~4.12.0": + version: 4.12.0 + resolution: "lodash._basetostring@npm:4.12.0" + checksum: 10c0/08d4f8affb3cfed7cc50bbe35d35605597bd72c05e6ed0696122b0a4da242de1b5f5396e986645f46a97e976d6f7b4943a001b002a4e2b3b8ea087949dc9eb98 + languageName: node + linkType: hard + +"lodash._baseuniq@npm:~4.6.0": + version: 4.6.0 + resolution: "lodash._baseuniq@npm:4.6.0" + dependencies: + lodash._createset: "npm:~4.0.0" + lodash._root: "npm:~3.0.0" + checksum: 10c0/07e2ac63efde634685ed12b16664ee04931b258cd2511b703fddd9ddfc624a85542e3f03a6c2fdac369c00559296198daa8efffaf90d71c7a35f5c89f94ebb14 + languageName: node + linkType: hard + +"lodash._createset@npm:~4.0.0": + version: 4.0.3 + resolution: "lodash._createset@npm:4.0.3" + checksum: 10c0/6144f59a63cedb8bf2840970579dc7dfdad55970741a80f9a6e5c6b29b629fc204c846e54e29266ec24b41e3f680bcbeb9fe9332fb9f2bab71eb9c70cacd26d8 + languageName: node + linkType: hard + +"lodash._root@npm:~3.0.0": + version: 3.0.1 + resolution: "lodash._root@npm:3.0.1" + checksum: 10c0/679c8a570381795b6953ec8c680442acb5472c5b399263ed4ea6839630cf4dd5d65aa8c2a0f6934507fc5bc70f2af20bc430cbd847728b8c1672db95555edb51 + languageName: node + linkType: hard + +"lodash._stringtopath@npm:~4.8.0": + version: 4.8.0 + resolution: "lodash._stringtopath@npm:4.8.0" + dependencies: + lodash._basetostring: "npm:~4.12.0" + checksum: 10c0/5e3a58a97b481a9069fc43ca6ddeace83891e3d9cff8d36400064c85977c225621a9c52131224d64632a054a7c19d8ca7b2f0f5bfc983402564bd2a5e49d8672 + languageName: node + linkType: hard + +"lodash.startcase@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.startcase@npm:4.4.0" + checksum: 10c0/bd82aa87a45de8080e1c5ee61128c7aee77bf7f1d86f4ff94f4a6d7438fc9e15e5f03374b947be577a93804c8ad6241f0251beaf1452bf716064eeb657b3a9f0 + languageName: node + linkType: hard + +"lodash.throttle@npm:^4.1.1": + version: 4.1.1 + resolution: "lodash.throttle@npm:4.1.1" + checksum: 10c0/14628013e9e7f65ac904fc82fd8ecb0e55a9c4c2416434b1dd9cf64ae70a8937f0b15376a39a68248530adc64887ed0fe2b75204b2c9ec3eea1cb2d66ddd125d + languageName: node + linkType: hard + +"lodash.uniqby@npm:4.5.0": + version: 4.5.0 + resolution: "lodash.uniqby@npm:4.5.0" + dependencies: + lodash._baseiteratee: "npm:~4.7.0" + lodash._baseuniq: "npm:~4.6.0" + checksum: 10c0/4f479ec2dd92825c2a308986d3613e32fe02b13dffc5d0998cbb1ef4eeee56835f13a88e093c7a88473fa72cf5ffd37f807f5ae2d9496e738d776c1911fdabc8 + languageName: node + linkType: hard + +"lodash@npm:^4.18.1": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + +"lru-cache@npm:^11.2.6": + version: 11.5.3 + resolution: "lru-cache@npm:11.5.3" + checksum: 10c0/2ad9bce6343a3544a1fd15364d8b35609aa52b2593d21fdc4877cabfe003b0f3deca4f9d6e9d13262f06e9e3d159de45c0a2f3edfc3adb46192333b5f146d8ed + languageName: node + linkType: hard + +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"magicast@npm:^0.5.2": + version: 0.5.5 + resolution: "magicast@npm:0.5.5" + dependencies: + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + source-map-js: "npm:^1.2.1" + checksum: 10c0/d27e94cd1e85f1736f97ab0c6e542170a818c7e573261722d056b3ec4a520e16d836ce4c6b0cd3dd215b1171d83592d48a3736f3da5997a55629a0de05f66e7d + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"mdn-data@npm:2.27.1": + version: 2.27.1 + resolution: "mdn-data@npm:2.27.1" + checksum: 10c0/eb8abf5d22e4d1e090346f5e81b67d23cef14c83940e445da5c44541ad874dc8fb9f6ca236e8258c3a489d9fb5884188a4d7d58773adb9089ac2c0b966796393 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mime-match@npm:^1.0.2": + version: 1.0.2 + resolution: "mime-match@npm:1.0.2" + dependencies: + wildcard: "npm:^1.1.0" + checksum: 10c0/f7f465246abe7798ed4b2072d9474fe86c8bbee6ea6169abb22eceab4a9752b0393272be3efd8620d3214d421a55420663416a1b4e728daba0f431da55030309 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mri@npm:^1.2.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"namespace-emitter@npm:^2.0.1": + version: 2.0.1 + resolution: "namespace-emitter@npm:2.0.1" + checksum: 10c0/847fdc3cc68fc9ba0f8959f240bf530f5c0b33c679079aa7756a39954cfef13f397bb4e4209387032a73f10a73684c75df7c6f60794e03c2b5786eda57bea756 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.20": + version: 3.3.20 + resolution: "nanoid@npm:3.3.20" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/856e8bf3bddb959d65997bd94908be030dbc5e6ed88fb3af2a638e51d1938f4d4f454c7c7231aba340775224f88b8cfd3ce1693c70471bfd05f5b6a2227c0c1e + languageName: node + linkType: hard + +"nanoid@npm:^6.0.1": + version: 6.0.2 + resolution: "nanoid@npm:6.0.2" + bin: + nanoid: bin/nanoid.js + checksum: 10c0/3c0223d2e658e6bab838af37112731df28eb5146b982e1b89bc1889b7a1476e0a36b4aa37ac6a567e7ebaf22a690d7916a80143a84cef77bd77416d0f1d20b9c + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.1.0 + resolution: "node-gyp@npm:13.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.7" + tinyglobby: "npm:^0.2.12" + undici: "npm:^8.4.1" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/c6693e52e0e111b21f5cd8ff303b41b51f40338adcc79ac5c6a02399eba7039d50dc816dcafcc485870291a641f7cb75db8887a9bdd0de537fe635a3a81cba5b + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + +"obug@npm:^2.1.1": + version: 2.2.1 + resolution: "obug@npm:2.2.1" + checksum: 10c0/8a7c001847d880fa6ba83c7bbee6a80fd271ee628439cd508c8647d53b217abb1759e2d5e4aaeb09a35d0a274bdb225a0a5a78eded3213d63e656153cf411b63 + languageName: node + linkType: hard + +"outdent@npm:^0.5.0": + version: 0.5.0 + resolution: "outdent@npm:0.5.0" + checksum: 10c0/e216a4498889ba1babae06af84cdc4091f7cac86da49d22d0163b3be202a5f52efcd2bcd3dfca60a361eb3a27b4299f185c5655061b6b402552d7fcd1d040cff + languageName: node + linkType: hard + +"p-filter@npm:^2.1.0": + version: 2.1.0 + resolution: "p-filter@npm:2.1.0" + dependencies: + p-map: "npm:^2.0.0" + checksum: 10c0/5ac34b74b3b691c04212d5dd2319ed484f591c557a850a3ffc93a08cb38c4f5540be059c6b10a185773c479ca583a91ea00c7d6c9958c815e6b74d052f356645 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 10c0/735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043 + languageName: node + linkType: hard + +"p-queue@npm:^9.3.3": + version: 9.3.5 + resolution: "p-queue@npm:9.3.5" + dependencies: + eventemitter3: "npm:^5.0.4" + p-timeout: "npm:^7.0.0" + checksum: 10c0/44652095edcf36e44bafda8fd6bb6be2d96e1fc05982f0f26afd768e0b07e228dc206b07b3c303771494cecfd28e5797f8e8771c9d5db280561e031fbfcb57a5 + languageName: node + linkType: hard + +"p-retry@npm:^8.0.0": + version: 8.0.1 + resolution: "p-retry@npm:8.0.1" + dependencies: + is-network-error: "npm:^1.3.0" + checksum: 10c0/3ce078a179ba2303d29d516db57abed71abd940c29aff292f97f64e2682777b0cb2b03321f4e3badb8185137a3a85cc003ba8060a769b179a5ba8059e7ecdd2c + languageName: node + linkType: hard + +"p-timeout@npm:^7.0.0": + version: 7.0.2 + resolution: "p-timeout@npm:7.0.2" + checksum: 10c0/26595bbda27361a15231a82d8d3ef4f277bb3e9647aaa4a554b90012d03d22aaf28165b60afffe9425fb1326bd40b93f145d76eb23adb6b76d07d5f5d4c7964a + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-manager-detector@npm:^0.2.0": + version: 0.2.11 + resolution: "package-manager-detector@npm:0.2.11" + dependencies: + quansync: "npm:^0.2.7" + checksum: 10c0/247991de461b9e731f3463b7dae9ce187e53095b7b94d7d96eec039abf418b61ccf74464bec1d0c11d97311f33472e77baccd4c5898f77358da4b5b33395e0b1 + languageName: node + linkType: hard + +"parse5@npm:^8.0.0": + version: 8.0.1 + resolution: "parse5@npm:8.0.1" + dependencies: + entities: "npm:^8.0.0" + checksum: 10c0/c3c1c5aab55f6e4be5245599790e56e64be7764a4a0edd7f98db4fe3bb380f63add752fa047dff0496446c25f4104f0c7c1967723de640bde92306a7bb67ed2f + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.3.1": + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3, picomatch@npm:^4.0.4, picomatch@npm:^4.0.7": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"playwright-core@npm:1.64.1": + version: 1.64.1 + resolution: "playwright-core@npm:1.64.1" + bin: + playwright-core: cli.js + checksum: 10c0/91c4f5ed1cff4b0e18b6ac62f66770f5f1b6c204d4aebd6a321c07466dca18a70822ac692597c6059705a703f9768097ac551df179fb49294d639509148e04f3 + languageName: node + linkType: hard + +"playwright@npm:1.64.1": + version: 1.64.1 + resolution: "playwright@npm:1.64.1" + dependencies: + playwright-core: "npm:1.64.1" + bin: + playwright: cli.js + checksum: 10c0/edf52d0a79feca37bf383e841a467aa68750684128218eac560063991c9e4b615a94901f580cd8a088bb05fd53008ab4a0e5e8d976067698b60ae5d1de2459f3 + languageName: node + linkType: hard + +"postcss@npm:^8.5.30": + version: 8.5.32 + resolution: "postcss@npm:8.5.32" + dependencies: + nanoid: "npm:^3.3.20" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.2" + checksum: 10c0/5fa6cad84e277c2ced965dc7e8e7c0b8d7e5b76a6835591b57a43fde46e671d0c8428d365e9141c080abaae5441d495ed77ffd7472c5bd41d6c2154bd03c01d7 + languageName: node + linkType: hard + +"preact@npm:^10.29.2": + version: 10.29.8 + resolution: "preact@npm:10.29.8" + peerDependencies: + preact-render-to-string: ">=5" + peerDependenciesMeta: + preact-render-to-string: + optional: true + checksum: 10c0/505460352139e0ce5b9c971b69d8d9f3b73ea0f316e3ff1c0b3db9a1abe206c9dff6ba385eb796253c38fcc08b5ade70cd8da3d9d6c9e9aec1a2fd59348253ae + languageName: node + linkType: hard + +"prettier@npm:^2.7.1": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b + languageName: node + linkType: hard + +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: "npm:^4.2.4" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/2f265dbad15897a43110a02dae55105c04d356ec4ed560723dcb9f0d34bc4fb2f13f79bb930e7561be10278e2314db5aca2527d5d3dcbbdee5e6b331d1571f6d + languageName: node + linkType: hard + +"punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"quansync@npm:^0.2.7": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10c0/cb9a1f8ebce074069f2f6a78578873ffedd9de9f6aa212039b44c0870955c04a71c3b1311b5d97f8ac2f2ec476de202d0a5c01160cb12bc0a11b7ef36d22ef56 + languageName: node + linkType: hard + +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"read-yaml-file@npm:^1.1.0": + version: 1.1.0 + resolution: "read-yaml-file@npm:1.1.0" + dependencies: + graceful-fs: "npm:^4.1.5" + js-yaml: "npm:^3.6.1" + pify: "npm:^4.0.1" + strip-bom: "npm:^3.0.0" + checksum: 10c0/85a9ba08bb93f3c91089bab4f1603995ec7156ee595f8ce40ae9f49d841cbb586511508bd47b7cf78c97f678c679b2c6e2c0092e63f124214af41b6f8a25ca31 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rolldown@npm:~1.2.14": + version: 1.2.15 + resolution: "rolldown@npm:1.2.15" + dependencies: + "@oxc-project/types": "npm:=0.155.0" + "@rolldown/binding-android-arm-eabi": "npm:1.2.15" + "@rolldown/binding-android-arm64": "npm:1.2.15" + "@rolldown/binding-darwin-arm64": "npm:1.2.15" + "@rolldown/binding-darwin-x64": "npm:1.2.15" + "@rolldown/binding-freebsd-x64": "npm:1.2.15" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.15" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.15" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.15" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.15" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.15" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.15" + "@rolldown/binding-linux-x64-musl": "npm:1.2.15" + "@rolldown/binding-openharmony-arm64": "npm:1.2.15" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.15" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.15" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm-eabi": + optional: true + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/084cbdcc222e534c9aff90e77488cdeb6a685a20a34e6e3ba77c3b8646723cea2f090f05522f452ed51217cce68b7aa67df8661b49b4ac0227a5edfcb0383b68 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.3": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"shallow-equal@npm:^3.1.0": + version: 3.1.0 + resolution: "shallow-equal@npm:3.1.0" + checksum: 10c0/57079f8741a86b25229b58b86df5d503414aca324e486ec7ddb2423db65914c80c8282149ef66a6803092847dc0d840da8fc338ea5027bbf4fc877f944df9bf6 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1, source-map-js@npm:^1.2.2": + version: 1.2.2 + resolution: "source-map-js@npm:1.2.2" + checksum: 10c0/d5d6dec62266834f83dd4f8a2606a196fb1a006ca5d53b0ef80ee513b7eba2f4e2ae241f4bc129ae7e9b80dea24f6e3d92b446b7e6e96be7653cab8888168a94 + languageName: node + linkType: hard + +"spawndamnit@npm:^3.0.1": + version: 3.0.1 + resolution: "spawndamnit@npm:3.0.1" + dependencies: + cross-spawn: "npm:^7.0.5" + signal-exit: "npm:^4.0.1" + checksum: 10c0/a9821a59bc78a665bd44718dea8f4f4010bb1a374972b0a6a1633b9186cda6d6fd93f22d1e49d9944d6bb175ba23ce29036a4bd624884fb157d981842c3682f3 + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^4.0.0-rc.1": + version: 4.3.0 + resolution: "std-env@npm:4.3.0" + checksum: 10c0/1e90dc69f32f853375c6de45dbe7219c8399e2a3ab3a972fdcd1cf3c20fe0e3c13bf58d57d6193b551d37a81c50dfb3f51e07774e9e48a7dc5a22dabcad7acbd + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"symbol-tree@npm:^3.2.4": + version: 3.2.4 + resolution: "symbol-tree@npm:3.2.4" + checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 + languageName: node + linkType: hard + +"tar@npm:^7.5.7": + version: 7.5.22 + resolution: "tar@npm:7.5.22" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/1311f6be85a8157ac4c9147bae43e13923d2a1aae15e4aa1bd5239e4e03d2cf53cfe103dde7f35832fbb4c938b042856bc8e9a0afd29abd05e2d1608788c4fea + languageName: node + linkType: hard + +"term-size@npm:^2.1.0": + version: 2.2.1 + resolution: "term-size@npm:2.2.1" + checksum: 10c0/89f6bba1d05d425156c0910982f9344d9e4aebf12d64bfa1f460d93c24baa7bc4c4a21d355fbd7153c316433df0538f64d0ae6e336cc4a69fdda4f85d62bc79d + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.2": + version: 1.3.1 + resolution: "tinyexec@npm:1.3.1" + checksum: 10c0/c590369cb3fa73cc18a3998caec8a00f9712d91309c881be3ac52b160fb2f80c7b1fbf3591858ab6abab47ad55385b0c86ec19982e32441a01f55298dab9ccd2 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.1.0": + version: 3.2.0 + resolution: "tinyrainbow@npm:3.2.0" + checksum: 10c0/22d9c303cd807026898dcb5ac5ffbf725b5a2cc8d042449769aed8f1266be94de5e95206b76cac948f68facafba5b80e9a9257c82e5fed2651be3fecf2b624ab + languageName: node + linkType: hard + +"tldts-core@npm:^7.4.19": + version: 7.4.19 + resolution: "tldts-core@npm:7.4.19" + checksum: 10c0/fd43d3d590e9a04c57035dc25aea992a5b33e593f4b0d80a08f57cbbb7964f9b7b4b7db212d0408142671a7715f49e8a6eed27ab5b28343ecec8e0383b6e9d98 + languageName: node + linkType: hard + +"tldts@npm:^7.0.5": + version: 7.4.19 + resolution: "tldts@npm:7.4.19" + dependencies: + tldts-core: "npm:^7.4.19" + bin: + tldts: bin/cli.js + checksum: 10c0/86a35f5508cd8a1d4e6516d54f0aa375efa643efd4a5d61c84f844f26c07fdea9a29320c341398127fa0d67877697820177f5362108cbcbf6ea06c1ed0795854 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"tough-cookie@npm:^6.0.0": + version: 6.0.2 + resolution: "tough-cookie@npm:6.0.2" + dependencies: + tldts: "npm:^7.0.5" + checksum: 10c0/5ff521a476a3c540821352125a5d481c8d2fe16035de7e0efda4df120f290c95500a0e9b51ea0aa56343955be482e014c30f5ba73e04b93ba138e4e855cb9e89 + languageName: node + linkType: hard + +"tr46@npm:^6.0.0": + version: 6.0.0 + resolution: "tr46@npm:6.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e + languageName: node + linkType: hard + +"transloadit-bubble-plugin@workspace:.": + version: 0.0.0-use.local + resolution: "transloadit-bubble-plugin@workspace:." + dependencies: + "@biomejs/biome": "npm:2.4.16" + "@changesets/cli": "npm:^2.31.0" + "@playwright/test": "npm:1.64.1" + "@types/node": "npm:^22" + "@uppy/core": "npm:6.3.0" + "@uppy/dashboard": "npm:6.0.2" + "@uppy/transloadit": "npm:6.0.1" + "@vitest/coverage-v8": "npm:4.1.11" + esbuild: "npm:0.28.2" + jsdom: "npm:^28.1.0" + typescript: "npm:6.0.3" + vitest: "npm:4.1.11" + languageName: unknown + linkType: soft + +"tus-js-client@npm:^4.3.1": + version: 4.3.1 + resolution: "tus-js-client@npm:4.3.1" + dependencies: + buffer-from: "npm:^1.1.2" + combine-errors: "npm:^3.0.3" + is-stream: "npm:^2.0.0" + js-base64: "npm:^3.7.2" + lodash.throttle: "npm:^4.1.1" + proper-lockfile: "npm:^4.1.2" + url-parse: "npm:^1.5.7" + checksum: 10c0/db36ec17cff0217f1199b84f355f8aa07abab47d98d003926f67d9bad08e15e77c3ef61f98aa360133f27ad4944cf519170ff5b5eb5b93a8a762c28dd49df59d + languageName: node + linkType: hard + +"typescript@npm:6.0.3": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A6.0.3#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici@npm:^7.21.0": + version: 7.30.1 + resolution: "undici@npm:7.30.1" + checksum: 10c0/c838089b010e8e28a9a5358d4bf4621e0c8d7bb70058b23e819667349c4c0b6791a7c8bc9bbe94ff0923db132faae0d9d9592b9743eda4f2b49466fa57482213 + languageName: node + linkType: hard + +"undici@npm:^8.4.1": + version: 8.12.3 + resolution: "undici@npm:8.12.3" + checksum: 10c0/714919e62b4160eea195fb35c7f5c4907235741e47bfde8a014ac58f6d24df13434e23400d7f48e2c455fd2b259d27f07fce514f04a5b4e99936ba62cbc4bb71 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"url-parse@npm:^1.5.7": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": + version: 8.3.5 + resolution: "vite@npm:8.3.5" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.7" + postcss: "npm:^8.5.30" + rolldown: "npm:~1.2.14" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.7.1 || ^0.8.0 + 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 + dependenciesMeta: + fsevents: + optional: true + 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 + bin: + vite: bin/vite.js + checksum: 10c0/33991600925b6717429c14a2c2907554aed077489ee6ff7815f9f49d5de559f5173ccb0472f2b3f9eef2a49e2e014f6cb624db8d4adebb37baad3a8cbc15065a + languageName: node + linkType: hard + +"vitest@npm:4.1.11": + version: 4.1.11 + resolution: "vitest@npm:4.1.11" + dependencies: + "@vitest/expect": "npm:4.1.11" + "@vitest/mocker": "npm:4.1.11" + "@vitest/pretty-format": "npm:4.1.11" + "@vitest/runner": "npm:4.1.11" + "@vitest/snapshot": "npm:4.1.11" + "@vitest/spy": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^4.0.0-rc.1" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running: "npm:^2.3.0" + 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.11 + "@vitest/browser-preview": 4.1.11 + "@vitest/browser-webdriverio": 4.1.11 + "@vitest/coverage-istanbul": 4.1.11 + "@vitest/coverage-v8": 4.1.11 + "@vitest/ui": 4.1.11 + 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 + bin: + vitest: ./vitest.mjs + checksum: 10c0/3fa0948cf74adcccc8cbcdb4e6d30ada6933bdfb1816ff99200f3d3b689325b37dc483b22535b57b6d911f7a7b64eaa6a5f8da1606cfe7f2466f48000c21e296 + languageName: node + linkType: hard + +"w3c-xmlserializer@npm:^5.0.0": + version: 5.0.0 + resolution: "w3c-xmlserializer@npm:5.0.0" + dependencies: + xml-name-validator: "npm:^5.0.0" + checksum: 10c0/8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b + languageName: node + linkType: hard + +"webidl-conversions@npm:^8.0.1": + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46 + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-mimetype@npm:5.0.0" + checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4 + languageName: node + linkType: hard + +"whatwg-url@npm:^16.0.0": + version: 16.0.1 + resolution: "whatwg-url@npm:16.0.1" + dependencies: + "@exodus/bytes": "npm:^1.11.0" + tr46: "npm:^6.0.0" + webidl-conversions: "npm:^8.0.1" + checksum: 10c0/e75565566abf3a2cdbd9f06c965dbcccee6ec4e9f0d3728ad5e08ceb9944279848bcaa211d35a29cb6d2df1e467dd05cfb59fbddf8a0adcd7d0bce9ffb703fd2 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"wildcard@npm:^1.1.0": + version: 1.1.2 + resolution: "wildcard@npm:1.1.2" + checksum: 10c0/4051a90884f60c2e7eed81811cbb21b2de37986c0f2f82f081b2a43b9693d5d240e80a98ede68a6fa3fb0779bcdf26720e954a890de26ae4e73ee5f04e233649 + languageName: node + linkType: hard + +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 10c0/3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5 + languageName: node + linkType: hard + +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard From 0c6020628c900a0b2b01cf86fb866c569b34fd1a Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 29 Oct 2026 21:39:00 +0100 Subject: [PATCH 02/16] Keep inline Dashboard sized to Bubble and add browser evidence --- html_headers.html | 2 +- src/types.ts | 1 + src/uploader.ts | 11 ++++++++++- test/e2e/bubble.test.mjs | 7 +++++++ test/e2e/harness.mjs | 12 ------------ test/evidence/after-desktop.png | Bin 0 -> 17695 bytes test/evidence/after-inline-desktop.png | Bin 0 -> 12918 bytes test/evidence/after-inline-mobile.png | Bin 0 -> 8700 bytes test/evidence/after-mobile.png | Bin 0 -> 10609 bytes test/evidence/before-desktop.png | Bin 0 -> 30230 bytes test/evidence/before-mobile.png | Bin 0 -> 20198 bytes 11 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 test/evidence/after-desktop.png create mode 100644 test/evidence/after-inline-desktop.png create mode 100644 test/evidence/after-inline-mobile.png create mode 100644 test/evidence/after-mobile.png create mode 100644 test/evidence/before-desktop.png create mode 100644 test/evidence/before-mobile.png diff --git a/html_headers.html b/html_headers.html index dcf435b..8464d24 100644 --- a/html_headers.html +++ b/html_headers.html @@ -2,7 +2,7 @@ `) } else if (path === '/') { response.setHeader('Content-Type', 'text/html') response.end(`${headers} diff --git a/test/evidence/after-desktop.png b/test/evidence/after-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..ca84e71f45b40b91f4fa2df788a2c6aa466fb000 GIT binary patch literal 17695 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 literal 0 HcmV?d00001 diff --git a/test/evidence/after-inline-desktop.png b/test/evidence/after-inline-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8be1a12817f03fc9ac074da78473f680fc3ab0 GIT binary patch literal 12918 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

HI-2OKTXdMmy{38NiAvoW_I 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 literal 0 HcmV?d00001 From 29f2610e61a4d9f3336ce34cbcbe652afd90a6a0 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 29 Oct 2026 21:51:47 +0100 Subject: [PATCH 03/16] Fix Bubble defaults, cancellation and workflow event regressions --- html_headers.html | 2 +- shared_tech_params.json | 2 +- src/uploader.ts | 28 +++++++++--- test/e2e/bubble.test.mjs | 16 +++++++ test/e2e/harness.mjs | 6 ++- test/evidence/completed-desktop.png | Bin 0 -> 11354 bytes test/evidence/completed-mobile.png | Bin 0 -> 8144 bytes test/evidence/error-desktop.png | Bin 0 -> 15106 bytes test/evidence/error-mobile.png | Bin 0 -> 11468 bytes test/evidence/selected-desktop.png | Bin 0 -> 11423 bytes test/evidence/selected-mobile.png | Bin 0 -> 8316 bytes test/unit/uploader.test.ts | 65 ++++++++++++++++++++++++---- 12 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 test/evidence/completed-desktop.png create mode 100644 test/evidence/completed-mobile.png create mode 100644 test/evidence/error-desktop.png create mode 100644 test/evidence/error-mobile.png create mode 100644 test/evidence/selected-desktop.png create mode 100644 test/evidence/selected-mobile.png diff --git a/html_headers.html b/html_headers.html index 8464d24..e519a84 100644 --- a/html_headers.html +++ b/html_headers.html @@ -2,7 +2,7 @@ `) diff --git a/test/evidence/completed-desktop.png b/test/evidence/completed-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..d84e84853cab7134a5018443298f834880f78161 GIT binary patch literal 11354 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

u=`(1 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%0A7=3oCN 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 P2coZIbp74W4v+r>C&)v1 literal 0 HcmV?d00001 diff --git a/test/evidence/error-desktop.png b/test/evidence/error-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..29a7631b2677883a9e602b5b15958cec6f5e68be GIT binary patch literal 15106 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 literal 0 HcmV?d00001 diff --git a/test/evidence/error-mobile.png b/test/evidence/error-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..a5b093615f853ae0c28cbb2615926bced3139189 GIT binary patch literal 11468 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 literal 0 HcmV?d00001 diff --git a/test/evidence/selected-mobile.png b/test/evidence/selected-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..d71d508b1c614314818d687786389860b2fc79cb GIT binary patch literal 8316 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 { getPlugin(name: string) { return this.plugins.get(name) } - cancelAll = vi.fn() + cancelAll = vi.fn(() => this.emit('cancel-all')) destroy() { this.destroyed = true } @@ -102,7 +102,7 @@ describe('upload states and failures', () => { it('publishes real results before signaling completion, and clears stale results on the next upload', () => { const client = clients[0] - client.emit('upload') + client.emit('upload', 'upload-1') expect(states.is_uploading).toBe(true) expect(instance.triggerEvent).toHaveBeenCalledWith('upload_started') client.emit('progress', 64) @@ -117,11 +117,11 @@ describe('upload states and failures', () => { expect(states.result_urls).toEqual(['https://example.com/file.png']) expect(JSON.parse(String(states.assembly_json))).toEqual(assembly) expect(JSON.parse(String(states.results_json))).toEqual(assembly.results) - client.emit('complete', { failed: [] }) + client.emit('complete', { uploadID: 'upload-1', successful: [{}], failed: [] }) expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') expect(states.is_uploading).toBe(false) expect(states.progress).toBe(100) - client.emit('upload') + client.emit('upload', 'upload-1') expect(states.result_urls).toEqual([]) expect(states.assembly_json).toBe('') }) @@ -129,14 +129,13 @@ describe('upload states and failures', () => { it.each([ 'error', 'upload-error', - 'restriction-failed', 'transloadit:assembly-error', ])('publishes %s without a false completed event', (event) => { const client = clients[0] - client.emit('upload') + client.emit('upload', 'upload-1') const error = new Error('API request failed') client.emit(event, ...(event === 'error' ? [error] : [null, error])) - client.emit('complete', { failed: [] }) + client.emit('complete', { uploadID: 'upload-1', successful: [{}], failed: [] }) expect(states.error_message).toBe('API request failed') expect(states.is_uploading).toBe(false) expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') @@ -148,7 +147,7 @@ describe('upload states and failures', () => { }) it('cancels uploads and resets all states', () => { - clients[0].emit('upload') + clients[0].emit('upload', 'upload-1') reset(instance) expect(clients[0].cancelAll).toHaveBeenCalledOnce() expect(states).toMatchObject({ is_uploading: false, progress: 0, error_message: '' }) @@ -197,7 +196,7 @@ describe('upload states and failures', () => { }) it('does not destroy an active upload when Bubble changes the display mode', () => { - clients[0].emit('upload') + clients[0].emit('upload', 'upload-1') update(instance, { ...properties, inline_dashboard: false }, context) expect(clients).toHaveLength(1) expect(clients[0].destroyed).toBe(false) @@ -261,3 +260,51 @@ describe('legacy open action', () => { vi.unstubAllGlobals() }) }) + +describe('council regression cases', () => { + beforeEach(() => { + initialize(instance, context) + update(instance, properties, context) + }) + + it.each(['', ' , '])('treats blank allowed types (%j) as unrestricted', (allowed_file_types) => { + update(instance, { ...properties, allowed_file_types }, context) + expect(clients[0].setOptions).toHaveBeenLastCalledWith({ + restrictions: expect.objectContaining({ allowedFileTypes: null }), + }) + }) + + it('signals an upload failure only once across overlapping Uppy error events', () => { + const client = clients[0] + client.emit('upload', 'upload-1') + const error = new Error('Assembly failed') + client.emit('upload-error', null, error) + client.emit('error', error) + client.emit('transloadit:assembly-error', {}, error) + expect( + vi.mocked(instance.triggerEvent).mock.calls.filter(([event]) => event === 'error'), + ).toHaveLength(1) + }) + + it('reports rejected additions without failing the active upload', () => { + const client = clients[0] + client.emit('upload', 'upload-1') + client.emit('restriction-failed', null, new Error('Too many files')) + expect(states.is_uploading).toBe(true) + expect(states.error_message).toBe('Too many files') + client.emit('complete', { uploadID: 'upload-1', successful: [{}], failed: [] }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it('ignores cancelled completions even after a subsequent upload starts', () => { + const client = clients[0] + client.emit('upload', 'cancelled-upload') + reset(instance) + client.emit('upload', 'next-upload') + client.emit('complete', { uploadID: 'cancelled-upload', successful: [], failed: [] }) + expect(states.is_uploading).toBe(true) + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + client.emit('complete', { uploadID: 'next-upload', successful: [{}], failed: [] }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) +}) From faf174b491dabe04459542d62e4d5d468772e1e0 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 29 Oct 2026 22:08:52 +0100 Subject: [PATCH 04/16] Isolate uploader sessions and preserve retry and background completion --- .github/workflows/release.yaml | 9 ++- CONTRIBUTING.md | 2 +- README.md | 3 +- html_headers.html | 2 +- src/uploader.ts | 93 +++++++++++++++++++----------- test/e2e/bubble.test.mjs | 18 ++++++ test/evidence/selected-mobile.png | Bin 8316 -> 9905 bytes test/unit/uploader.test.ts | 75 +++++++++++++++++++++++- 8 files changed, 159 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bca4f8f..f2c9a6e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,14 +20,17 @@ jobs: node-version: '26.12.1' - run: npm install --global corepack@0.36.0 && corepack enable - run: corepack yarn install --immutable - - uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d + - id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d with: version: corepack yarn changeset:version:release commitMode: github-api env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: corepack yarn release:prepare - - uses: actions/upload-artifact@v7 + - if: steps.changesets.outputs.hasChangesets == 'false' + run: corepack yarn release:prepare + - if: steps.changesets.outputs.hasChangesets == 'false' + uses: actions/upload-artifact@v7 with: name: bubble-candidate-${{ github.sha }} path: build/bubble-plugin-*.zip* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec53431..d40ab7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ Add a user-facing Changeset with `corepack yarn changeset`. On main, Changesets that updates `package.json` and `CHANGELOG.md`. Merge the version PR, then download that commit's `bubble-candidate-` artifact from **Release candidate** Actions. The archive and SHA-256 file contain only Bubble's export. Locally, `corepack yarn release:prepare` produces the same handoff. -A candidate is built for every main commit; only use the version PR's commit for publication. +Candidates are built on main only when no Changesets remain pending; use the version PR's commit for publication. Changesets versions are repository candidates; record Bubble's separately assigned published version in the release notes. There is deliberately no automatic Bubble deploy or user migration. diff --git a/README.md b/README.md index 4f2d2e1..f5f6ac9 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ verified after publication before linking builders to it. 4. On **Encoding completed**, read **Result URLs** (a list of texts), **Results JSON**, or **Assembly JSON**. Other states: **Assembly ID**, **Is uploading**, **Upload progress** (0–100, transfer progress; processing may continue), and **Error message**. **Upload started** and - **Upload error** events allow loading and failure workflows. Bubble's reset resets the uploader. + **Upload error** events allow loading and failure workflows. Use Done or Bubble's reset before + the next batch; one batch owns the result states at a time. The `params` string supports a Template ID: diff --git a/html_headers.html b/html_headers.html index e519a84..005cf06 100644 --- a/html_headers.html +++ b/html_headers.html @@ -2,7 +2,7 @@ `) + `) } else { response.writeHead(404).end() } @@ -63,9 +64,14 @@ export async function startHarness() { response.writeHead(500).end('Harness error') } }) - await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + if (!address || typeof address === 'string') throw new Error('Harness did not bind a TCP port') return { - url: `http://127.0.0.1:${server.address().port}`, - close: () => new Promise((resolve) => server.close(resolve)), + url: `http://127.0.0.1:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }), } } diff --git a/test/unit/uploader.test.ts b/test/unit/uploader.test.ts index d945c0f..8999af6 100644 --- a/test/unit/uploader.test.ts +++ b/test/unit/uploader.test.ts @@ -1,8 +1,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { Context, Instance } from '../../src/types' +import type { Context, Instance } from '../../src/types.ts' -import { assemblyOptions, initialize, open, reset, update } from '../../src/uploader' +import { assemblyOptions, initialize, open, reset, update } from '../../src/uploader.ts' const { clients, MockUppy } = vi.hoisted(() => { const clients: MockUppy[] = [] diff --git a/tsconfig.json b/tsconfig.json index 0a00696..3a10108 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,17 @@ { - "include": ["src"], + "include": ["src", "scripts", "test", "*.config.ts"], "compilerOptions": { "target": "es2022", - "module": "esnext", - "moduleResolution": "bundler", + "module": "nodenext", + "moduleResolution": "nodenext", "lib": ["es2022", "dom"], "strict": true, "noEmit": true, "erasableSyntaxOnly": true, "isolatedModules": true, "skipLibCheck": true, - "types": [] + "types": ["node"], + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true } } diff --git a/vitest.config.mjs b/vitest.config.ts similarity index 100% rename from vitest.config.mjs rename to vitest.config.ts From 16d3230d226527574af5888ebf3300dc6179b32e Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 30 Oct 2026 19:35:53 +0100 Subject: [PATCH 15/16] Leave harness outputs unpublished until element initialization --- test/e2e/browser.ts | 15 ++++----------- test/e2e/bubble.test.ts | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/e2e/browser.ts b/test/e2e/browser.ts index c2fc849..b1bb73d 100644 --- a/test/e2e/browser.ts +++ b/test/e2e/browser.ts @@ -10,7 +10,7 @@ interface HarnessInstance extends Instance { declare global { interface Window { - states: { + states: Partial<{ is_uploading: boolean progress: number error_message: string @@ -18,7 +18,7 @@ declare global { assembly_json: string results_json: string result_urls: string[] - } + }> events: string[] instance: HarnessInstance context: Context @@ -35,15 +35,8 @@ declare global { export function start(defaults: Properties): void { const canvas = document.querySelector('#canvas') if (!(canvas instanceof HTMLElement)) throw new Error('Missing harness canvas') - window.states = { - is_uploading: false, - progress: 0, - error_message: '', - assembly_id: '', - assembly_json: '', - results_json: '', - result_urls: [], - } + // Leave outputs unpublished until the generated element supplies them. + window.states = {} window.events = [] window.instance = { canvas: { 0: canvas }, diff --git a/test/e2e/bubble.test.ts b/test/e2e/bubble.test.ts index c6092d7..5f14893 100644 --- a/test/e2e/bubble.test.ts +++ b/test/e2e/bubble.test.ts @@ -39,6 +39,8 @@ for (const width of [1280, 390]) { page, }) => { await page.setViewportSize({ width, height: 800 }) + // Only the generated element may publish outputs; the harness must not supply them. + expect(await page.evaluate(() => window.states)).toEqual({}) await page.evaluate(async () => { await window.mount({ inline_dashboard: true }) }) @@ -83,7 +85,7 @@ test('publishes a visible configuration error and recovers after reset', async ( await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') await page.getByRole('button', { name: /Upload 1 file/ }).click() await expect.poll(() => page.evaluate(() => window.events.includes('error'))).toBe(true) - expect(await page.evaluate(() => window.states.error_message.length > 0)).toBe(true) + expect(await page.evaluate(() => Boolean(window.states.error_message))).toBe(true) await page.evaluate(() => window.resetElement(window.instance, window.context)) expect(await page.evaluate(() => window.states.error_message)).toBe('') }) @@ -153,17 +155,20 @@ test('uploads a real file and exposes real processed results from the built Bubb 1, ) const result = await page.evaluate(() => { - const assembly: { ok: string; uploads: unknown[] } = JSON.parse(window.states.assembly_json) - const results: { resize: { meta: { width: number; height: number } }[] } = JSON.parse( - window.states.results_json, - ) + const { assembly_json, results_json, result_urls } = window.states + if (!assembly_json || !results_json || !result_urls?.length) { + throw new Error('The element did not publish processed Assembly outputs') + } + const assembly: { ok: string; uploads: unknown[] } = JSON.parse(assembly_json) + const results: { resize: { meta: { width: number; height: number } }[] } = + JSON.parse(results_json) const file = results.resize[0] return { status: assembly.ok, count: assembly.uploads.length, width: file.meta.width, height: file.meta.height, - url: window.states.result_urls[0], + url: result_urls[0], } }) expect(result.status).toBe('ASSEMBLY_COMPLETED') @@ -186,8 +191,10 @@ test('uploads a real file and exposes real processed results from the built Bubb { timeout: 100_000 }, ) .toBe(2) - expect(await page.evaluate(() => window.states.result_urls.length)).toBe(1) - const second = await request.get(await page.evaluate(() => window.states.result_urls[0])) + expect(await page.evaluate(() => window.states.result_urls?.length)).toBe(1) + const secondUrl = await page.evaluate(() => window.states.result_urls?.[0]) + if (!secondUrl) throw new Error('The element did not publish a result URL after reset') + const second = await request.get(secondUrl) expect(second.ok()).toBe(true) expect((await second.body()).length).toBeGreaterThan(0) }) From 0b27c38a433495ee315ac0a82ac3b11333d9faba Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 30 Oct 2026 19:40:18 +0100 Subject: [PATCH 16/16] Preserve browser-only globals during TypeScript checks --- CONTRIBUTING.md | 3 ++- package.json | 2 +- tsconfig.browser.json | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tsconfig.browser.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b33aa0a..4bb03a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,8 @@ Bubble remains the primary plugin store. Keep `actions/`, `elements/`, `api/`, never rename existing opaque IDs. New element `AAu` coexists with legacy `AAp`. Handwritten code lives in TypeScript: `src/`, `scripts/`, `test/` and the root test configs. -Node runs scripts with native type stripping; `tsc --noEmit` checks them all. Bubble callbacks +Node runs scripts with native type stripping; `tsc --noEmit` checks them all. A separate browser +config keeps Node globals out of `src/` and the browser harness. Bubble callbacks remain generated JavaScript for editor sync; the legacy `AAp` export stays unchanged. Edit TypeScript in `src/`, then run `corepack yarn build` and commit the generated callbacks and diff --git a/package.json b/package.json index 59b5511..527ae69 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "scripts": { "build": "node scripts/build.ts", "check": "yarn lint && yarn test && yarn build && git diff --exit-code -- actions elements html_headers.html", - "lint": "biome check . && tsc --noEmit", + "lint": "biome check . && tsc --noEmit && tsc --noEmit -p tsconfig.browser.json", "fix": "biome check --write .", "test": "vitest run --coverage", "test:browser": "yarn build && playwright test", diff --git a/tsconfig.browser.json b/tsconfig.browser.json new file mode 100644 index 0000000..ccbbba2 --- /dev/null +++ b/tsconfig.browser.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src", "test/e2e/browser.ts"], + "compilerOptions": { + "types": [] + } +}