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..9aaa355 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,54 @@ +name: Release candidate +on: + push: + branches: [main] +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false +jobs: + version: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + outputs: + hasChangesets: ${{ steps.changesets.outputs.hasChangesets }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + # Changesets uses its own API token; installs do not need a write credential. + persist-credentials: false + - 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 + - id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d + with: + version: corepack yarn changeset:version:release + commitMode: github-api + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + candidate: + needs: version + if: needs.version.outputs.hasChangesets == 'false' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - 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 + # Changesets writes ~/.netrc. This separate runner keeps its token away from candidate checks. + - 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..4bb03a5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# 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`. + +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. 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 +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`. Before merging, close and reopen it as a maintainer +if CI is absent: GitHub does not trigger PR workflows for `GITHUB_TOKEN` writes. Wait for its checks +to pass. 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. +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. + +## 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. +Uppy's `errorReporting: false` only disables request-level reports in the pinned version. Its +plugin error handler still sends messages, browser user agent and client identification to +`transloaditstatus.com/client_error`, including validation failures; this is not a telemetry opt-out. +Uploads retain the original one-file limit. Multi-file batches need separate result aggregation and +partial-retry handling; exposing a larger limit before those exist can silently lose processed results. diff --git a/README.md b/README.md index a39c7d8..3e29e24 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,71 @@ -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 and maximum bytes + per file (default 5 MiB). Each upload accepts one local file, matching the original plugin. +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. 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: + +```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..74a6839 --- /dev/null +++ b/elements/AAu/initialize.js @@ -0,0 +1,10 @@ +function(instance, context) { + // Generated by yarn build. + instance.data.initializationFailed = false; + instance.data.ready = window.TransloaditBubbleReady.then(function(api) { api.initialize(instance, context); return api; }); + instance.data.ready.catch(function(error) { + instance.data.initializationFailed = true; + 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..7bea005 --- /dev/null +++ b/elements/AAu/params.json @@ -0,0 +1,123 @@ +{ + "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 + }, + "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..34ed38a --- /dev/null +++ b/elements/AAu/reset.js @@ -0,0 +1,8 @@ +function(instance, context) { + // Generated by yarn build. + instance.data.ready.then(function(api) { api.reset(instance, context); }).catch(function(error) { + if (instance.data.initializationFailed) return; + 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..0666d55 --- /dev/null +++ b/elements/AAu/update.js @@ -0,0 +1,8 @@ +function(instance, properties, context) { + // Generated by yarn build. + instance.data.ready.then(function(api) { api.update(instance, properties, context); }).catch(function(error) { + if (instance.data.initializationFailed) return; + instance.publishState('error_message', error.message); + instance.triggerEvent('error'); + }); +} diff --git a/html_headers.html b/html_headers.html index 9f0bee8..73192a6 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..527ae69 --- /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.ts", + "check": "yarn lint && yarn test && yarn build && git diff --exit-code -- actions elements html_headers.html", + "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", + "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.ts" + }, + "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.ts b/playwright.config.ts new file mode 100644 index 0000000..6ceffc0 --- /dev/null +++ b/playwright.config.ts @@ -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.ts b/scripts/build.ts new file mode 100644 index 0000000..429bcd5 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,66 @@ +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. +// The versioned Transloadit CDN is a trusted runtime dependency; keep its pin and typed API in sync. +const cdn = 'https://releases.transloadit.com/uppy/v6.0.5/uppy.min' +const modules: Record = { + '@uppy/core': ['Uppy', '6.3.0'], + '@uppy/dashboard': ['Dashboard', '6.0.2'], + '@uppy/transloadit': ['Transloadit', '6.0.1'], +} +const { dependencies }: { dependencies: Record } = 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});` + const start = + method === 'initialize' + ? 'instance.data.initializationFailed = false;\n instance.data.ready = ' + : '' + const failure = + method === 'initialize' + ? 'instance.data.initializationFailed = true;' + : 'if (instance.data.initializationFailed) return;' + await writeFile( + `elements/AAu/${method}.js`, + `function(${args}) {\n // Generated by yarn build.\n ${start}${ready}.then(function(api) { ${call} })${method === 'initialize' ? ';\n instance.data.ready' : ''}.catch(function(error) {\n ${failure}\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.ts b/scripts/prepare-release.ts new file mode 100644 index 0000000..a1f9c11 --- /dev/null +++ b/scripts/prepare-release.ts @@ -0,0 +1,28 @@ +import { execFileSync } from 'node:child_process' +import { createHash } from 'node:crypto' +import { mkdir, readFile, writeFile } from 'node:fs/promises' + +const { version }: { version: string } = JSON.parse(await readFile('package.json', 'utf8')) +await mkdir('build', { recursive: true }) +const file = `build/bubble-plugin-${version}.zip` +// CI artifacts include the commit SHA; this filename labels a candidate, not a published Bubble version. +// 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: unknown) => { + if (!(error instanceof Error && 'code' in error && 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..cc737c1 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 (unused; local files only)", + "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..5fe6f61 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,35 @@ +import type Uppy from '@uppy/core' + +export interface Properties { + params?: string | null + signature?: string + inline_dashboard?: boolean + button_label?: string + 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 + resizeObserver?: ResizeObserver + dispose?: () => void + mode?: boolean + isUploading?: boolean + failed?: boolean + configurationError?: string + errorIsConfiguration?: boolean + assemblyId?: string + failedAssemblies?: Set + 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..7b1d4c1 --- /dev/null +++ b/src/uploader.ts @@ -0,0 +1,382 @@ +import Uppy from '@uppy/core' +import Dashboard from '@uppy/dashboard' +import Transloadit, { type AssemblyResponse } from '@uppy/transloadit' + +import type { Context, Instance, Properties } from './types.ts' + +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) +} +function currentAssembly(instance: Instance, assembly?: { assembly_id?: string } | null) { + return !assembly?.assembly_id || assembly.assembly_id === instance.data.assemblyId +} +function report(instance: Instance, error: unknown, configuration = false) { + const message = error instanceof Error ? error.message : String(error) + const duplicate = configuration && instance.data.configurationError === message + if (configuration) instance.data.configurationError = message + instance.data.errorIsConfiguration = message === instance.data.configurationError + instance.publishState('error_message', message) + if (!duplicate) instance.triggerEvent('error') +} +export function fail(instance: Instance, error: unknown) { + const alreadyFailed = instance.data.failed + instance.data.failed = true + instance.data.isUploading = false + instance.publishState('is_uploading', false) + if (!alreadyFailed) report(instance, 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 ?? '' + if (!params.trim()) throw new Error('Provide signed Assembly params JSON before uploading') + 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 } +} +function destroy(instance: Instance) { + const uppy = instance.data.uppy + instance.data.uppy = undefined + instance.data.resizeObserver?.disconnect() + const dashboard = uppy?.getPlugin('Dashboard') + if (instance.data.mode === false && dashboard?.isModalOpen()) { + // destroy() alone leaves Dashboard's document keyboard listener behind. + dashboard.setOptions({ animateOpenClose: false }) + void dashboard.closeModal() + } + uppy?.destroy() +} +export function initialize(instance: Instance, _context: Context) { + instance.data.dispose?.() + resetStates(instance) + 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 + const dispose = () => { + instance.data.observer?.disconnect() + destroy(instance) + root.remove() + instance.data.root = undefined + resetStates(instance) + } + instance.data.dispose = dispose + // Bubble has no teardown callback; ancestor page/group removals also need to dispose this root. + instance.data.observer = new MutationObserver(() => { + if (instance.data.root === root && !root.isConnected) dispose() + }) + instance.data.observer.observe(document.body, { childList: true, subtree: true }) +} +export function update(instance: Instance, properties: Properties, context: Context) { + try { + instance.data.properties = properties + const types = properties.allowed_file_types?.split(/[\s,]+/).filter(Boolean) + const restrictions = { + // Preserve the original single-file contract: partial multi-file retries lose Assembly results. + maxNumberOfFiles: 1, + maxFileSize: positive(properties.max_file_size, 5 * 1024 * 1024), + allowedFileTypes: types?.length ? types : null, + } + if (!instance.data.root) initialize(instance, context) + const inline = properties.inline_dashboard !== false + if (!instance.data.uppy || (inline !== instance.data.mode && !instance.data.isUploading)) { + destroy(instance) + const root = instance.data.root + if (!root) throw new Error('Initialize the uploader before updating it') + root.replaceChildren() + // Bubble states describe one Assembly cycle at a time. + const uppy = new Uppy({ restrictions, autoProceed: false, allowMultipleUploadBatches: false }) + .use(Transloadit, { + waitForEncoding: true, + // This only disables request-level reports; Uppy still submits plugin error events. + errorReporting: false, + clientName: 'bubble-plugin', + assemblyOptions: () => { + try { + if (instance.data.configurationError) + throw new Error(instance.data.configurationError) + for (const file of uppy.getFiles()) { + const error = uppy.validateSingleFile(file) + if (error) throw new Error(error) + } + return assemblyOptions(instance.data.properties ?? {}, !instance.data.legacy) + } catch (error) { + // Uppy retryUpload/retryAll reject without emitting a preprocessor error event. + // Include the file so Dashboard keeps its Retry control after validation fails. + uppy.emit( + 'error', + error instanceof Error ? error : new Error(String(error)), + uppy.getFiles()[0], + ) + throw error + } + }, + }) + .use( + Dashboard, + inline + ? { target: root, inline: true, width: '100%', height: root.clientHeight || 350 } + : { target: root, inline: false, closeAfterFinish: true }, + ) + instance.data.uppy = uppy + instance.data.mode = inline + const failedAssemblies = new Set() + instance.data.failedAssemblies = failedAssemblies + let previousAssembly: AssemblyResponse | undefined + const current = () => instance.data.uppy === uppy + const refreshMode = () => + queueMicrotask(() => { + const latest = instance.data.properties ?? {} + if ( + current() && + !instance.data.isUploading && + (latest.inline_dashboard !== false) !== inline + ) + update(instance, latest, context) + }) + const failure = (error: unknown) => { + if (current()) { + if (instance.data.assemblyId) failedAssemblies.add(instance.data.assemblyId) + fail(instance, error) + refreshMode() + } + } + if (typeof ResizeObserver !== 'undefined') { + instance.data.resizeObserver = new ResizeObserver(() => { + if (current() && instance.data.mode) + uppy.getPlugin('Dashboard')?.setOptions({ height: root.clientHeight || 350 }) + }) + instance.data.resizeObserver.observe(root) + } + if (!inline) { + const button = document.createElement('button') + button.type = 'button' + button.dataset.transloaditUpload = '' + button.textContent = properties.button_label || 'Upload files' + button.onclick = () => { + void uppy.getPlugin('Dashboard')?.openModal() + } + root.appendChild(button) + } + uppy.on('upload', () => { + if (!current()) return + // Retire Uppy's old watcher too: transfer failures leave it listening for late status errors. + if (previousAssembly) uppy.emit('transloadit:assembly-cancel', previousAssembly) + previousAssembly = undefined + // Retrying supersedes the old Assembly before its replacement has been created. + instance.data.assemblyId = undefined + instance.data.failed = false + instance.data.isUploading = true + instance.data.errorIsConfiguration = !!instance.data.configurationError + publish(instance, { + ...empty(), + is_uploading: true, + error_message: instance.data.configurationError ?? '', + }) + instance.triggerEvent('upload_started') + }) + uppy.on('progress', (progress) => { + if (current() && instance.data.isUploading) instance.publishState('progress', progress) + }) + uppy.on('transloadit:assembly-created', (assembly, fileIDs = []) => { + if (!current()) return + previousAssembly = assembly + instance.data.assemblyId = assembly.assembly_id + // A replacement Assembly needs a new tus resource, not the previous Assembly's upload URL. + for (const id of fileIDs) { + const file = uppy.getFile(id) + if (file) + uppy.setFileState(id, { + tus: { ...file.tus, uploadUrl: undefined }, + progress: { + uploadStarted: null, + uploadComplete: false, + percentage: 0, + bytesUploaded: false, + bytesTotal: file.size, + }, + }) + } + instance.publishState('assembly_id', assembly.assembly_id) + }) + uppy.on('transloadit:complete', (assembly) => { + // Duplicate final requests from any previous cycle can resolve during the next upload. + if ( + !current() || + assembly.assembly_id !== instance.data.assemblyId || + failedAssemblies.has(assembly.assembly_id ?? '') + ) + return + 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 per-file retries omit the generic complete event; processing completion is authoritative. + if (!instance.data.failed && instance.data.isUploading) { + instance.data.isUploading = false + instance.data.errorIsConfiguration = !!instance.data.configurationError + publish(instance, { + is_uploading: false, + progress: 100, + error_message: instance.data.configurationError ?? '', + }) + instance.triggerEvent('completed') + refreshMode() + } + }) + uppy.on('error', failure) + uppy.on('upload-error', (_file, error) => failure(error)) + uppy.on('transloadit:assembly-error', (assembly, error) => { + if (currentAssembly(instance, assembly)) failure(error) + }) + uppy.on('restriction-failed', (_file, error) => { + if (!current()) return + report(instance, error) + }) + uppy.on('cancel-all', () => { + if (!current()) return + resetStates(instance) + // A fresh client prevents pending requests from a cancelled Assembly affecting its successor. + destroy(instance) + if (instance.data.legacy === undefined) + update(instance, instance.data.properties ?? {}, context) + }) + } else { + instance.data.uppy.setOptions({ restrictions }) + const button = instance.data.root?.querySelector('button[data-transloadit-upload]') + if (!instance.data.mode && button) + button.textContent = properties.button_label || 'Upload files' + } + if (instance.data.configurationError) { + instance.data.configurationError = undefined + if (instance.data.errorIsConfiguration) instance.publishState('error_message', '') + instance.data.errorIsConfiguration = false + } + } catch (error) { + // Property errors recover independently from transfer failures and never stop an active upload. + report(instance, error, true) + } +} +function resetStates(instance: Instance, configurationError?: string) { + instance.data.assemblyId = undefined + instance.data.isUploading = false + instance.data.failed = false + instance.data.configurationError = configurationError + instance.data.errorIsConfiguration = !!configurationError + publish(instance, { ...empty(), error_message: configurationError ?? '' }) +} +export function reset(instance: Instance) { + // Without a client, cancel-all cannot revalidate an unresolved property error. + resetStates(instance, instance.data.uppy ? undefined : instance.data.configurationError) + instance.data.uppy?.cancelAll() +} +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.parseInt(context.keys['MAX FILE SIZE'] ?? '', 10) || undefined, + allowed_file_types: context.keys['ALLOWED FILE TYPES'], + }, + context, + ) + const uppy = instance.data.uppy + let disposed = false + const dispose = () => { + if (disposed) return + disposed = true + instance.data.dispose?.() + canvas.remove() + } + if (!uppy) { + dispose() + return + } + instance.data.root?.querySelector('button[data-transloadit-upload]')?.remove() + let closed = false + const notifiedAssemblies = new Set() + uppy.on('transloadit:complete', (assembly) => { + const id = assembly.assembly_id + if ( + disposed || + !id || + id !== instance.data.assemblyId || + instance.data.failedAssemblies?.has(id) || + notifiedAssemblies.has(id) + ) + return + // Per-file retries can leave the modal open while duplicate final-status requests resolve. + notifiedAssemblies.add(id) + 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')) + if (closed) dispose() + }) + uppy.on('dashboard:modal-closed', () => { + closed = true + if (!instance.data.isUploading) dispose() + }) + uppy.on('cancel-all', dispose) + for (const event of ['error', 'upload-error'] as const) + uppy.on(event, () => { + if (closed) dispose() + }) + uppy.on('transloadit:assembly-error', (assembly) => { + if (closed && currentAssembly(instance, assembly)) dispose() + }) + void uppy.getPlugin('Dashboard')?.openModal() +} diff --git a/test/e2e/browser.ts b/test/e2e/browser.ts new file mode 100644 index 0000000..b1bb73d --- /dev/null +++ b/test/e2e/browser.ts @@ -0,0 +1,73 @@ +import type Uppy from '@uppy/core' + +import type { Context, Instance, Properties } from '../../src/types.ts' +import type { initialize, reset, update } from '../../src/uploader.ts' + +interface HarnessInstance extends Instance { + canvas: { 0: HTMLElement } + data: Instance['data'] & { ready?: Promise } +} + +declare global { + interface Window { + states: Partial<{ + is_uploading: boolean + progress: number + error_message: string + assembly_id: string + assembly_json: string + results_json: string + result_urls: string[] + }> + events: string[] + instance: HarnessInstance + context: Context + mount: (properties: Properties) => Promise + getUppy: () => Uppy + initializeElement: typeof initialize + updateElement: typeof update + resetElement: (instance: Instance, context: Context) => ReturnType + sources: { listeners: Record void> }[] + finalRequests: (() => void)[] + } +} + +export function start(defaults: Properties): void { + const canvas = document.querySelector('#canvas') + if (!(canvas instanceof HTMLElement)) throw new Error('Missing harness canvas') + // Leave outputs unpublished until the generated element supplies them. + window.states = {} + window.events = [] + window.instance = { + canvas: { 0: canvas }, + data: {}, + publishState(name, value) { + Object.assign(window.states, { [name]: value }) + }, + triggerEvent(name) { + window.events.push(name) + }, + } + window.context = { + keys: {}, + reportDebugger(message) { + throw new Error(message) + }, + } + window.getUppy = () => { + const uppy = window.instance.data.uppy + if (!uppy) throw new Error('Mount the uploader before accessing Uppy') + return uppy + } + window.mount = async (properties) => { + for (const name of ['initializeElement', 'updateElement', 'resetElement'] as const) { + const method = name.replace('Element', '') + const code = await fetch(`/elements/AAu/${method}.js`).then((response) => response.text()) + // Bubble stores callbacks as function expressions; evaluate the actual generated export. + window[name] = new Function(`return (${code})`)() + } + window.initializeElement(window.instance, window.context) + window.updateElement(window.instance, { ...defaults, ...properties }, window.context) + await window.instance.data.ready + } +} diff --git a/test/e2e/bubble.test.ts b/test/e2e/bubble.test.ts new file mode 100644 index 0000000..5f14893 --- /dev/null +++ b/test/e2e/bubble.test.ts @@ -0,0 +1,406 @@ +import { expect, type Page, test } from '@playwright/test' + +import { startHarness } from './harness.ts' + +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: Awaited> +const errors = new WeakMap() +test.beforeAll(async () => { + harness = await startHarness() +}) +test.afterAll(async () => { + await harness?.close() +}) +test.beforeEach(async ({ page }) => { + const issues: string[] = [] + 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) => { + const url = new URL(request.url()) + issues.push(`Failed request: ${url.protocol} ${url.origin} (${request.failure()?.errorText})`) + }) + 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 }) + // 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 }) + }) + await expect(page.getByRole('button', { name: 'browse files' })).toBeVisible() + expect((await page.locator('.uppy-Dashboard-inner').boundingBox())?.height).toBeGreaterThan(350) + await page.evaluate(() => { + window.instance.canvas[0].style.height = '500px' + }) + await expect + .poll(async () => (await page.locator('.uppy-Dashboard-inner').boundingBox())?.height) + .toBe(500) + 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.evaluate(() => window.resetElement(window.instance, window.context)) + await expect(page.getByRole('button', { name: 'Choose files', exact: true })).toBeVisible() + 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(() => 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('') +}) + +test('releases modal keyboard focus when Bubble resets the element', async ({ page }) => { + await page.evaluate(async () => { + await window.mount({ inline_dashboard: false }) + for (const label of ['Outside one', 'Outside two']) { + const button = document.createElement('button') + button.textContent = label + document.body.appendChild(button) + } + }) + await page.getByRole('button', { name: 'Upload files', exact: true }).click() + await expect(page.getByRole('button', { name: 'browse files' })).toBeVisible() + await page.evaluate(() => window.resetElement(window.instance, window.context)) + await page.getByRole('button', { name: 'Outside one', exact: true }).focus() + await page.keyboard.press('Tab') + await expect(page.getByRole('button', { name: 'Outside two', exact: true })).toBeFocused() +}) + +test('reports invalid signing data when a failed file is retried', async ({ page }) => { + await page.evaluate(async () => 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) + await page.evaluate(async () => { + window.updateElement( + window.instance, + { + inline_dashboard: true, + params: JSON.stringify({ auth: { key: 'test', expires: 'later' }, template_id: 'test' }), + signature: '', + }, + window.context, + ) + await Promise.resolve() + const uppy = window.getUppy() + await uppy.retryUpload(uppy.getFiles()[0].id).catch(() => {}) + }) + expect(await page.evaluate(() => window.states.is_uploading)).toBe(false) + expect(await page.evaluate(() => window.states.error_message)).toContain('server-signed params') + expect(await page.evaluate(() => window.events.filter((event) => event === 'error').length)).toBe( + 2, + ) + await expect(page.getByRole('button', { name: 'Retry upload', exact: true })).toBeVisible() +}) + +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 () => 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) + await page.evaluate(async () => { + const options = await fetch('/options').then((response) => response.json()) + window.updateElement(window.instance, { ...options, inline_dashboard: true }, window.context) + }) + await page.getByRole('button', { name: 'Retry upload', exact: true }).click() + await expect + .poll(() => page.evaluate(() => window.events.includes('completed')), { timeout: 100_000 }) + .toBe(true) + expect(await page.evaluate(() => window.events.filter((event) => event === 'error').length)).toBe( + 1, + ) + const result = await page.evaluate(() => { + 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: 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) + await page.evaluate(async () => { + window.resetElement(window.instance, window.context) + const options = await fetch('/options').then((response) => response.json()) + window.updateElement(window.instance, { ...options, inline_dashboard: true }, window.context) + }) + 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.filter((event) => event === 'completed').length), + { timeout: 100_000 }, + ) + .toBe(2) + 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) +}) + +test('updates only the launcher label and preserves the modal close button', async ({ page }) => { + await page.evaluate(async () => { + await window.mount({ inline_dashboard: false, button_label: 'First label' }) + }) + await page.evaluate(() => { + window.updateElement( + window.instance, + { inline_dashboard: false, button_label: 'New label' }, + window.context, + ) + }) + await expect(page.getByRole('button', { name: 'New label', exact: true })).toBeVisible() + await page.getByRole('button', { name: 'New label', exact: true }).click() + await expect(page.getByRole('button', { name: 'Close Modal', exact: true })).toHaveText('×') +}) + +for (const restrictions of [{ max_file_size: 1 }, { allowed_file_types: '.bin' }]) { + test(`rechecks a selected file after restrictions change: ${JSON.stringify(restrictions)}`, async ({ + page, + }) => { + await page.evaluate(async () => window.mount({ inline_dashboard: true })) + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + // Wait for selection to finish loading its preview before changing the file's restrictions. + await expect(page.locator('.uppy-Dashboard-Item-previewImg')).toBeVisible() + await expect + .poll(() => + page + .locator('.uppy-Dashboard-Item-previewImg') + .evaluate( + (image) => + image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0, + ), + ) + .toBe(true) + await page.evaluate((restrictions) => { + window.updateElement( + window.instance, + { inline_dashboard: true, ...restrictions }, + window.context, + ) + }, restrictions) + await page.getByRole('button', { name: /Upload 1 file/ }).click() + await expect.poll(() => page.evaluate(() => window.states.is_uploading)).toBe(false) + expect(await page.evaluate(() => window.states.error_message)).toMatch(/exceeds|only upload/i) + }) +} + +for (const phase of ['transfer', 'processing']) { + test(`starts a fresh tus upload after ${phase} failure`, async ({ page }) => { + const requests: string[] = [] + await page.route(`${harness.url}/assembly/**`, (route) => + route.fulfill({ + json: { assembly_id: 'fixture', ok: 'ASSEMBLY_UPLOADING', uploads: [], results: {} }, + }), + ) + await page.route(`${harness.url}/tus/**`, async (route) => { + const request = route.request() + const path = new URL(request.url()).pathname + requests.push(`${request.method()} ${path}`) + const headers = { 'Tus-Resumable': '1.0.0' } + if (request.method() === 'POST') { + await route.fulfill({ + status: 201, + headers: { ...headers, Location: `${harness.url}${path}file` }, + }) + } else if ( + phase === 'transfer' && + path.startsWith('/tus/1/') && + request.method() === 'PATCH' + ) { + // A protocol failure without an HTTP error or automatic retry: no data reaches Assembly 1. + await route.fulfill({ status: 204, headers }) + } else { + await route.fulfill({ + status: request.method() === 'HEAD' ? 200 : 204, + headers: { + ...headers, + 'Upload-Offset': request.method() === 'HEAD' ? '0' : '98', + 'Upload-Length': '98', + }, + }) + } + }) + await page.evaluate(async () => { + // Drive the actual processing-error path without an external API, SSE connection or telemetry. + window.sources = [] + Object.defineProperty(window, 'EventSource', { + configurable: true, + writable: true, + // The fixture implements only the SSE listener/close surface used by the pinned SDK. + value: class { + listeners: Record void> = {} + constructor() { + window.sources.push(this) + } + addEventListener(name: string, callback: (event: { data: string }) => void) { + this.listeners[name] = callback + } + close() {} + }, + }) + await window.mount({ + inline_dashboard: true, + params: JSON.stringify({ auth: { key: 'test', expires: 'later' }, template_id: 'test' }), + signature: 'test', + }) + const uppy = window.getUppy() + uppy.setOptions({ logger: { debug() {}, warn() {}, error() {} } }) + const tus = uppy.getPlugin('Tus') + if (!tus) throw new Error('Missing Tus plugin') + tus.setOptions({ retryDelays: [] }) + const transloadit = uppy.getPlugin('Transloadit') + if (!transloadit) throw new Error('Missing Transloadit plugin') + const client = transloadit.client + let number = 0 + client.createAssembly = async () => { + number += 1 + return { + assembly_id: `fixture-${number}`, + ok: 'ASSEMBLY_UPLOADING', + uploads: [], + results: {}, + assembly_url: `${location.origin}/assembly/${number}`, + assembly_ssl_url: `${location.origin}/assembly/${number}`, + tus_url: `${location.origin}/tus/${number}/`, + websocket_url: `${location.origin}/events`, + } + } + client.submitError = async () => ({ ok: 'ASSEMBLY_UPLOADING', assembly_id: 'fixture' }) + client.cancelAssembly = async () => {} + window.finalRequests = [] + client.getAssemblyStatus = (url) => + new Promise((resolve) => { + window.finalRequests.push(() => + resolve({ + assembly_id: `fixture-${url.split('/').at(-1)}`, + ok: 'ASSEMBLY_COMPLETED', + uploads: [], + results: {}, + }), + ) + }) + }) + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + await page.getByRole('button', { name: /Upload 1 file/ }).click() + if (phase === 'processing') { + await expect + .poll(() => page.evaluate(() => window.getUppy().getFiles()[0].progress.uploadComplete)) + .toBe(true) + await page.evaluate(() => + window.sources[0].listeners.assembly_error({ + data: JSON.stringify({ message: 'Intentional processing failure' }), + }), + ) + } + await expect.poll(() => page.evaluate(() => window.events.includes('error'))).toBe(true) + await page.getByRole('button', { name: 'Retry upload', exact: true }).first().click() + await expect.poll(() => requests.includes('POST /tus/2/')).toBe(true) + expect(requests).not.toContain('HEAD /tus/1/file') + await expect + .poll(() => page.evaluate(() => window.getUppy().getFiles()[0].progress.uploadComplete)) + .toBe(true) + // An already pending status request can fail after its Assembly's connection was closed. + await page.evaluate(() => + window.sources[0].listeners.assembly_error({ + data: JSON.stringify({ message: 'Late previous Assembly error' }), + }), + ) + expect(await page.evaluate(() => window.states.is_uploading)).toBe(true) + expect(await page.evaluate(() => window.states.error_message)).toBe('') + await page.evaluate(() => { + // Polling and SSE can independently request the same final status. + window.sources[1].listeners.message({ data: 'assembly_finished' }) + window.sources[1].listeners.message({ data: 'assembly_finished' }) + window.finalRequests[0]() + }) + await expect.poll(() => page.evaluate(() => window.events.includes('completed'))).toBe(true) + expect(await page.evaluate(() => window.states.assembly_id)).toBe('fixture-2') + const client = await page.evaluateHandle(() => window.getUppy()) + await page.getByRole('button', { name: 'Done', exact: true }).click() + await page.locator('input[type=file]').first().setInputFiles('test/fixtures/upload.png') + await page.getByRole('button', { name: /Upload 1 file/ }).click() + await expect.poll(() => requests.includes('POST /tus/3/')).toBe(true) + expect(await page.evaluate((previous) => window.getUppy() === previous, client)).toBe(true) + await page.evaluate(() => window.finalRequests[1]()) + expect(await page.evaluate(() => window.states.is_uploading)).toBe(true) + expect(await page.evaluate(() => window.states.assembly_id)).toBe('fixture-3') + expect(await page.evaluate(() => window.states.assembly_json)).toBe('') + expect( + await page.evaluate(() => window.events.filter((event) => event === 'completed').length), + ).toBe(1) + await page.evaluate(() => { + window.sources[2].listeners.message({ data: 'assembly_finished' }) + window.finalRequests[2]() + }) + await expect + .poll(() => + page.evaluate(() => window.events.filter((event) => event === 'completed').length), + ) + .toBe(2) + expect(await page.evaluate(() => window.states.assembly_id)).toBe('fixture-3') + await client.dispose() + await page.evaluate(() => window.resetElement(window.instance, window.context)) + }) +} diff --git a/test/e2e/harness.ts b/test/e2e/harness.ts new file mode 100644 index 0000000..2f079ae --- /dev/null +++ b/test/e2e/harness.ts @@ -0,0 +1,77 @@ +import { createHmac } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { createServer } from 'node:http' + +import { build } from 'esbuild' + +import type { Properties } from '../../src/types.ts' + +export async function startHarness(): Promise<{ url: string; close: () => Promise }> { + const headers = await readFile('html_headers.html', 'utf8') + const { + fields, + }: { + fields: Record + } = JSON.parse(await readFile('elements/AAu/params.json', 'utf8')) + const defaults = Object.fromEntries( + Object.values(fields).map((field) => [field.name, field.default_val]), + ) + const { outputFiles } = await build({ + entryPoints: ['test/e2e/browser.ts'], + bundle: true, + write: false, + target: 'es2022', + format: 'iife', + globalName: 'BubbleTestHarness', + }) + 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 === '/') { + 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)) + 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:${address.port}`, + close: () => + new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }), + } +} diff --git a/test/evidence/after-desktop.png b/test/evidence/after-desktop.png new file mode 100644 index 0000000..ca84e71 Binary files /dev/null and b/test/evidence/after-desktop.png differ diff --git a/test/evidence/after-inline-desktop.png b/test/evidence/after-inline-desktop.png new file mode 100644 index 0000000..4aa8cfc Binary files /dev/null and b/test/evidence/after-inline-desktop.png differ diff --git a/test/evidence/after-inline-mobile.png b/test/evidence/after-inline-mobile.png new file mode 100644 index 0000000..f2921f0 Binary files /dev/null and b/test/evidence/after-inline-mobile.png differ diff --git a/test/evidence/after-mobile.png b/test/evidence/after-mobile.png new file mode 100644 index 0000000..4111319 Binary files /dev/null and b/test/evidence/after-mobile.png differ diff --git a/test/evidence/before-desktop.png b/test/evidence/before-desktop.png new file mode 100644 index 0000000..838c8b0 Binary files /dev/null and b/test/evidence/before-desktop.png differ diff --git a/test/evidence/before-mobile.png b/test/evidence/before-mobile.png new file mode 100644 index 0000000..ad142af Binary files /dev/null and b/test/evidence/before-mobile.png differ diff --git a/test/evidence/completed-desktop.png b/test/evidence/completed-desktop.png new file mode 100644 index 0000000..d84e848 Binary files /dev/null and b/test/evidence/completed-desktop.png differ diff --git a/test/evidence/completed-mobile.png b/test/evidence/completed-mobile.png new file mode 100644 index 0000000..c4a8de7 Binary files /dev/null and b/test/evidence/completed-mobile.png differ diff --git a/test/evidence/error-desktop.png b/test/evidence/error-desktop.png new file mode 100644 index 0000000..57b1e7f Binary files /dev/null and b/test/evidence/error-desktop.png differ diff --git a/test/evidence/error-mobile.png b/test/evidence/error-mobile.png new file mode 100644 index 0000000..4a5f65b Binary files /dev/null and b/test/evidence/error-mobile.png differ diff --git a/test/evidence/selected-desktop.png b/test/evidence/selected-desktop.png new file mode 100644 index 0000000..fbab12a Binary files /dev/null and b/test/evidence/selected-desktop.png differ diff --git a/test/evidence/selected-mobile.png b/test/evidence/selected-mobile.png new file mode 100644 index 0000000..d71d508 Binary files /dev/null and b/test/evidence/selected-mobile.png differ diff --git a/test/fixtures/upload.png b/test/fixtures/upload.png new file mode 100644 index 0000000..d366955 Binary files /dev/null and b/test/fixtures/upload.png differ diff --git a/test/unit/bootstrap.test.ts b/test/unit/bootstrap.test.ts new file mode 100644 index 0000000..1602d97 --- /dev/null +++ b/test/unit/bootstrap.test.ts @@ -0,0 +1,23 @@ +import { readFileSync } from 'node:fs' + +import { afterEach, expect, it, vi } from 'vitest' + +afterEach(() => vi.unstubAllGlobals()) + +it('reports one CDN failure across initialization, updates and reset', async () => { + const ready = Promise.reject(new Error('CDN unavailable')) + vi.stubGlobal('window', { TransloaditBubbleReady: ready }) + const instance = { data: {}, publishState: vi.fn(), triggerEvent: vi.fn() } + const invoke = (method: string) => { + const callback = new Function(`return (${readFileSync(`elements/AAu/${method}.js`, 'utf8')})`)() + callback(instance, {}, {}) + } + invoke('initialize') + invoke('update') + invoke('reset') + await new Promise((resolve) => setTimeout(resolve, 0)) + invoke('update') + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(instance.publishState).toHaveBeenCalledWith('error_message', 'CDN unavailable') + expect(instance.triggerEvent).toHaveBeenCalledExactlyOnceWith('error') +}) diff --git a/test/unit/uploader.test.ts b/test/unit/uploader.test.ts new file mode 100644 index 0000000..8999af6 --- /dev/null +++ b/test/unit/uploader.test.ts @@ -0,0 +1,613 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import type { Context, Instance } from '../../src/types.ts' + +import { assemblyOptions, initialize, open, reset, update } from '../../src/uploader.ts' + +const { clients, MockUppy } = vi.hoisted(() => { + 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(), + closeModal: vi.fn(), + isModalOpen: () => false, + 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) + } + getFiles() { + return [] + } + cancelAll = vi.fn(() => this.emit('cancel-all')) + 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', 'upload-1') + 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', { uploadID: 'upload-1', successful: [{}], failed: [] }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + expect(states.is_uploading).toBe(false) + expect(states.progress).toBe(100) + client.emit('upload', 'upload-1') + expect(states.result_urls).toEqual([]) + expect(states.assembly_json).toBe('') + }) + + it.each([ + 'error', + 'upload-error', + 'transloadit:assembly-error', + ])('publishes %s without a false completed event', (event) => { + const client = clients[0] + client.emit('upload', 'upload-1') + const error = new Error('API request failed') + client.emit(event, ...(event === 'error' ? [error] : [null, error])) + 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') + }) + + 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', 'upload-1') + 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 () => { + clients[0].emit('upload', 'removed-upload') + document.body.replaceChildren() + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(clients[0].destroyed).toBe(true) + expect(instance.data.uppy).toBeUndefined() + expect(states.is_uploading).toBe(false) + }) + + 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_file_size: 2048, + allowed_file_types: '.png, image/jpeg', + }, + context, + ) + expect(button?.textContent).toBe('Add file') + expect(client.setOptions).toHaveBeenCalledWith({ + restrictions: { + maxNumberOfFiles: 1, + maxFileSize: 2048, + allowedFileTypes: ['.png', 'image/jpeg'], + }, + }) + }) + + it('defers display changes until an active upload has finished', async () => { + clients[0].emit('upload', 'upload-1') + update(instance, { ...properties, inline_dashboard: false }, context) + expect(clients).toHaveLength(1) + expect(clients[0].destroyed).toBe(false) + clients[0].emit('transloadit:assembly-created', { assembly_id: 'finished' }) + clients[0].emit('transloadit:complete', { assembly_id: 'finished', results: {} }) + await Promise.resolve() + expect(instance.data.root?.querySelector('button')?.textContent).toBe('Upload files') + }) + + it('reports invalid dynamic properties without failing an ongoing upload', () => { + clients[0].emit('upload', 'active-upload') + update(instance, { ...properties, max_file_size: -1 }, context) + expect(states.error_message).toContain('positive integers') + expect(states.is_uploading).toBe(true) + update(instance, properties, context) + clients[0].emit('transloadit:assembly-created', { assembly_id: 'finished' }) + clients[0].emit('transloadit:complete', { assembly_id: 'finished', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it('clears corrected property errors without hiding upload failures', () => { + update(instance, { ...properties, max_file_size: -1 }, context) + update(instance, properties, context) + expect(states.error_message).toBe('') + clients[0].emit('upload', 'failed-upload') + clients[0].emit('error', new Error('Transfer failed')) + update(instance, properties, context) + expect(states.error_message).toBe('Transfer failed') + }) + + it('reports a new property error after upload failure and deduplicates unchanged bad updates', () => { + clients[0].emit('upload', 'failed-upload') + clients[0].emit('error', new Error('Transfer failed')) + vi.mocked(instance.triggerEvent).mockClear() + update(instance, { ...properties, max_file_size: -1 }, context) + update(instance, { ...properties, max_file_size: -1 }, context) + expect(states.error_message).toContain('positive integers') + expect(instance.triggerEvent).toHaveBeenCalledExactlyOnceWith('error') + update(instance, properties, context) + update(instance, { ...properties, max_file_size: -1 }, context) + expect(instance.triggerEvent).toHaveBeenCalledTimes(2) + }) + + it('blocks new Assemblies until invalid properties are corrected', () => { + update(instance, { ...properties, max_file_size: -1, signature: 'sha384:fresh' }, context) + clients[0].emit('upload', 'blocked-upload') + const options = clients[0].getPlugin('Transloadit').opts + expect(() => options.assemblyOptions()).toThrow('positive integers') + expect(states.is_uploading).toBe(false) + expect(states.error_message).toContain('positive integers') + update(instance, { ...properties, signature: 'sha384:fresh' }, context) + expect(states.error_message).toBe('') + expect(options.assemblyOptions().signature).toBe('sha384:fresh') + }) + + it('retains property validation across rejected additions and reset', () => { + update(instance, { ...properties, max_file_size: -1 }, context) + vi.mocked(instance.triggerEvent).mockClear() + clients[0].emit('restriction-failed', null, new Error('Disallowed type')) + update(instance, { ...properties, max_file_size: -1 }, context) + expect(instance.triggerEvent).toHaveBeenCalledExactlyOnceWith('error') + reset(instance) + expect(states.error_message).toContain('positive integers') + reset(instance) + expect(states.error_message).toContain('positive integers') + }) + + it('finishes active processing without hiding an outstanding property error', () => { + clients[0].emit('upload', 'active-upload') + update(instance, { ...properties, max_file_size: -1 }, context) + clients[0].emit('transloadit:assembly-created', { assembly_id: 'finished' }) + clients[0].emit('transloadit:complete', { assembly_id: 'finished', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + expect(states.error_message).toContain('positive integers') + }) + + it.each([0, -1, 1.5, Number.NaN])('reports an invalid upload limit (%s)', (limit) => { + update(instance, { ...properties, max_file_size: limit }, context) + expect(states.error_message).toContain('positive integers') + expect(instance.triggerEvent).toHaveBeenCalledWith('error') + }) +}) + +describe('signing contract', () => { + it.each([undefined, null, '', ' '])('names missing Assembly params (%j)', (params) => { + expect(() => assemblyOptions({ params })).toThrow('Provide signed Assembly params JSON') + }) + 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.each([ + ['2048 bytes', 2048], + ['10.5', 10], + ])('preserves the legacy integer size parsing for %s', (size, expected) => { + open({}, { ...context, keys: { 'MAX FILE SIZE': size } }) + expect(clients[0].opts.restrictions).toMatchObject({ maxFileSize: expected }) + clients[0].emit('dashboard:modal-closed') + }) + + 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:assembly-created', { assembly_id: 'a' }) + 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() + }) +}) + +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('transloadit:assembly-created', { assembly_id: 'active' }) + client.emit('transloadit:complete', { assembly_id: 'active', results: {} }) + 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) + const next = clients.at(-1) + next?.emit('upload', 'next-upload') + client.emit('complete', { uploadID: 'cancelled-upload', successful: [], failed: [] }) + expect(states.is_uploading).toBe(true) + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + next?.emit('transloadit:assembly-created', { assembly_id: 'next' }) + next?.emit('transloadit:complete', { assembly_id: 'next', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) +}) + +describe('upload session isolation', () => { + beforeEach(() => { + initialize(instance, context) + update(instance, properties, context) + }) + + it('ignores late errors, progress and results from a cancelled session', () => { + const old = clients[0] + old.emit('upload', 'old-upload') + reset(instance) + const next = clients.at(-1) + next?.emit('upload', 'next-upload') + old.emit('error', new Error('All files were canceled after assembly was created')) + old.emit('progress', 100) + old.emit('transloadit:complete', { assembly_id: 'cancelled', results: {} }) + expect(states.is_uploading).toBe(true) + expect(states.error_message).toBe('') + expect(states.progress).toBe(0) + expect(states.assembly_id).toBe('') + }) + + it('completes a successful per-file retry without a generic Uppy complete event', () => { + const client = clients[0] + client.emit('upload', 'failed-upload') + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'retried' }) + client.emit('transloadit:complete', { assembly_id: 'retried', results: {} }) + expect(states.is_uploading).toBe(false) + expect(states.progress).toBe(100) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it('ignores a failed Assembly status request that resolves after its retry begins', () => { + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'failed' }) + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + client.emit('transloadit:complete', { assembly_id: 'failed', results: {} }) + expect(states.assembly_id).toBe('retry') + expect(states.is_uploading).toBe(true) + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + client.emit('transloadit:complete', { assembly_id: 'retry', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it.each([false, true])('ignores superseded successful completions (created=%s)', (created) => { + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'first' }) + const first = { assembly_id: 'first', results: {} } + client.emit('transloadit:complete', first) + instance.triggerEvent = vi.fn() + client.emit('upload', 'second-upload') + if (created) client.emit('transloadit:assembly-created', { assembly_id: 'second' }) + client.emit('transloadit:complete', first) + expect(states.is_uploading).toBe(true) + expect(states.assembly_json).toBe('') + expect(instance.triggerEvent).not.toHaveBeenCalledWith('completed') + if (!created) client.emit('transloadit:assembly-created', { assembly_id: 'second' }) + client.emit('transloadit:complete', { assembly_id: 'second', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it.each([ + false, + true, + ])('ignores old Assembly errors after retry starts (created=%s)', (created) => { + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'old' }) + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + if (created) client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + client.emit( + 'transloadit:assembly-error', + { assembly_id: 'old' }, + new Error('Late status error'), + ) + expect(states.is_uploading).toBe(true) + expect(states.error_message).toBe('') + if (!created) client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + client.emit('transloadit:complete', { assembly_id: 'retry', results: {} }) + expect(instance.triggerEvent).toHaveBeenLastCalledWith('completed') + }) + + it('allows a detached Bubble canvas to be attached and updated again', async () => { + const canvas = '0' in instance.canvas ? instance.canvas[0] : instance.canvas + canvas.remove() + await new Promise((resolve) => setTimeout(resolve, 0)) + document.body.appendChild(canvas) + update(instance, properties, context) + expect(instance.data.root?.isConnected).toBe(true) + }) +}) + +describe('legacy background upload', () => { + it('notifies only once for duplicate final-status responses after a retry', () => { + const fetch = vi.fn().mockResolvedValue({ ok: true }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'first' }) + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + const result = { assembly_id: 'retry', results: {} } + client.emit('transloadit:complete', result) + client.emit('transloadit:complete', result) + expect(fetch).toHaveBeenCalledOnce() + client.emit('dashboard:modal-closed') + vi.unstubAllGlobals() + }) + it('keeps a dismissed retry alive when the previous Assembly errors late', () => { + const fetch = vi.fn().mockResolvedValue({ ok: true }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'old' }) + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + client.emit('dashboard:modal-closed') + client.emit( + 'transloadit:assembly-error', + { assembly_id: 'old' }, + new Error('Late status error'), + ) + expect(client.destroyed).toBe(false) + client.emit('transloadit:complete', { assembly_id: 'retry', results: {} }) + expect(fetch).toHaveBeenCalledExactlyOnceWith('https://example.com/hook', expect.any(Object)) + expect(client.destroyed).toBe(true) + vi.unstubAllGlobals() + }) + + it('does not notify for a failed Assembly completing after retry', () => { + const fetch = vi.fn().mockResolvedValue({ ok: true }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + const client = clients[0] + client.emit('upload', 'first-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'failed' }) + client.emit('error', new Error('Transfer failed')) + client.emit('upload', 'retry-upload') + client.emit('transloadit:assembly-created', { assembly_id: 'retry' }) + client.emit('transloadit:complete', { assembly_id: 'failed', results: {} }) + expect(fetch).not.toHaveBeenCalled() + client.emit('cancel-all') + vi.unstubAllGlobals() + }) + + it.each([ + 'error', + 'upload-error', + 'transloadit:assembly-error', + ])('releases a dismissed picker on %s', (event) => { + open(properties, context) + const client = clients[0] + client.emit('upload', 'legacy-upload') + client.emit('dashboard:modal-closed') + const error = new Error('Upload failed') + client.emit(event, ...(event === 'error' ? [error] : [null, error])) + expect(client.destroyed).toBe(true) + }) + + it('keeps a dismissed upload alive until it sends its notification', async () => { + const fetch = vi.fn().mockResolvedValue({ ok: true }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + const client = clients[0] + client.emit('upload', 'legacy-upload') + client.emit('dashboard:modal-closed') + expect(client.destroyed).toBe(false) + client.emit('transloadit:assembly-created', { assembly_id: 'finished' }) + client.emit('transloadit:complete', { assembly_id: 'finished', results: {} }) + expect(fetch).toHaveBeenCalledOnce() + expect(client.destroyed).toBe(true) + vi.unstubAllGlobals() + }) +}) + +it('preserves initial configuration errors across resets before Uppy exists', () => { + initialize(instance, context) + update(instance, { ...properties, max_file_size: 0 }, context) + expect(clients).toHaveLength(0) + reset(instance) + reset(instance) + expect(states.error_message).toContain('positive integers') + expect(instance.triggerEvent).toHaveBeenCalledExactlyOnceWith('error') +}) + +it('does not notify from a cancelled legacy picker', () => { + const fetch = vi.fn().mockResolvedValue({ ok: true }) + vi.stubGlobal('fetch', fetch) + open(properties, { ...context, keys: { 'NOTIFY URL': 'https://example.com/hook' } }) + clients[0].emit('cancel-all') + clients[0].emit('transloadit:complete', { assembly_id: 'cancelled', results: {} }) + expect(fetch).not.toHaveBeenCalled() + vi.unstubAllGlobals() +}) 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": [] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3a10108 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["src", "scripts", "test", "*.config.ts"], + "compilerOptions": { + "target": "es2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "lib": ["es2022", "dom"], + "strict": true, + "noEmit": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "skipLibCheck": true, + "types": ["node"], + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4b98862 --- /dev/null +++ b/vitest.config.ts @@ -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