From 0121d64a69b5effc26f6518da05f6e5c982c1909 Mon Sep 17 00:00:00 2001 From: Talha Date: Thu, 27 Aug 2026 18:00:21 +0500 Subject: [PATCH 1/8] feat(premiere): add project media indexing and search --- .gitignore | 2 + INSTALLATION_GUIDE.md | 4 + README.md | 1 + docs/integrations/premiere-pro.md | 111 + docs/local-api.md | 18 + premiere/README.md | 71 + premiere/docs/MANUAL_TEST_CHECKLIST.md | 77 + premiere/eslint.config.mjs | 30 + premiere/index.tsx | 35 + premiere/package-lock.json | 4296 +++++++++++++++++++++++ premiere/package.json | 42 + premiere/public/index.html | 13 + premiere/public/manifest.json | 47 + premiere/public/styles.css | 446 +++ premiere/src/premiere/adapter.ts | 104 + premiere/src/premiere/library.ts | 69 + premiere/src/premiere/types.ts | 28 + premiere/src/services/vidxp/client.ts | 226 ++ premiere/src/services/vidxp/types.ts | 112 + premiere/src/ui/App.tsx | 644 ++++ premiere/src/ui/components/Spectrum.tsx | 247 ++ premiere/src/ui/spectrum-elements.d.ts | 29 + premiere/src/ui/theme.ts | 42 + premiere/tests/library.test.ts | 72 + premiere/tests/theme.test.ts | 41 + premiere/tests/vidxp-client.test.ts | 124 + premiere/tsconfig.json | 24 + premiere/vite.config.mjs | 31 + src/vidxp/api_routes/media.py | 74 + src/vidxp/control_plane.py | 28 + src/vidxp/upload_service.py | 2 +- tests/test_api.py | 143 +- tests/test_control_plane.py | 24 + 33 files changed, 7255 insertions(+), 2 deletions(-) create mode 100644 docs/integrations/premiere-pro.md create mode 100644 premiere/README.md create mode 100644 premiere/docs/MANUAL_TEST_CHECKLIST.md create mode 100644 premiere/eslint.config.mjs create mode 100644 premiere/index.tsx create mode 100644 premiere/package-lock.json create mode 100644 premiere/package.json create mode 100644 premiere/public/index.html create mode 100644 premiere/public/manifest.json create mode 100644 premiere/public/styles.css create mode 100644 premiere/src/premiere/adapter.ts create mode 100644 premiere/src/premiere/library.ts create mode 100644 premiere/src/premiere/types.ts create mode 100644 premiere/src/services/vidxp/client.ts create mode 100644 premiere/src/services/vidxp/types.ts create mode 100644 premiere/src/ui/App.tsx create mode 100644 premiere/src/ui/components/Spectrum.tsx create mode 100644 premiere/src/ui/spectrum-elements.d.ts create mode 100644 premiere/src/ui/theme.ts create mode 100644 premiere/tests/library.test.ts create mode 100644 premiere/tests/theme.test.ts create mode 100644 premiere/tests/vidxp-client.test.ts create mode 100644 premiere/tsconfig.json create mode 100644 premiere/vite.config.mjs diff --git a/.gitignore b/.gitignore index 1e94432..ef41dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ pnpm-debug.log* /target /desktop/node_modules/ /desktop/dist/ +/premiere/dist/ +/premiere/coverage/ /desktop/src-tauri/target/ /desktop/src-tauri/gen/ /desktop/src-tauri/binaries/uv-* diff --git a/INSTALLATION_GUIDE.md b/INSTALLATION_GUIDE.md index 9288c41..6394426 100644 --- a/INSTALLATION_GUIDE.md +++ b/INSTALLATION_GUIDE.md @@ -79,6 +79,10 @@ setup leaves the previous working setup available. - **App integration service** lets other local applications use the HTTP API or Streamable HTTP MCP. +The [Premiere Pro extension preview](docs/integrations/premiere-pro.md) uses +the app integration service to search media that is already loaded in an +editing project. + Choose where models should be stored, then decide whether to download them during setup. VidXP displays the required downloads before starting them. diff --git a/README.md b/README.md index 7f08f1b..ff00f7e 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ ordinary MCP tools. - [Python, HTTP, and MCP installation](INSTALLATION_GUIDE.md) - [Local HTTP API and MCP server](docs/local-api.md) +- [Premiere Pro extension preview](docs/integrations/premiere-pro.md) - [ChatGPT and Codex plugin integration](docs/integrations/openai-plugin.md) - [Optional capability packages](INSTALLATION_GUIDE.md#optional-dependency-extras) - [Coolify server setup](docs/deployment/coolify.md) diff --git a/docs/integrations/premiere-pro.md b/docs/integrations/premiere-pro.md new file mode 100644 index 0000000..118ce6b --- /dev/null +++ b/docs/integrations/premiere-pro.md @@ -0,0 +1,111 @@ +# Search Premiere Pro media with VidXP + +The VidXP Premiere Pro extension preview lets an editor choose clips or bins +from the open Premiere project, index the underlying video files, and search +the resulting library without leaving Premiere. Search features are discovered +from the connected VidXP runtime, so the panel does not assume a fixed list of +capabilities. + +This is a source-built proof of concept. It targets Premiere Pro 25.6 or newer +on the current UXP platform. It has automated client and build coverage, but +the repository does not yet claim host validation or a packaged `.ccx` +release. + +## Prepare VidXP Desktop + +The extension and VidXP must run on the same computer because the panel passes +Premiere's existing local media paths to VidXP. It does not upload or duplicate +the source videos. + +1. In VidXP Desktop, enable **Local video processing** and + **App integration service** for the selected installation. +2. Prepare the search features you intend to use. +3. Start **Local video processing**. +4. Under **App integration service**, choose **Start locally** and copy the + displayed **API address**. + +Keep the service private to the computer. The Premiere preview does not need +the shared-network mode or a bearer token for this setup. + +The command-line equivalent is: + +```bash +uv tool install --python 3.14 --torch-backend cpu \ + "vidxp[local-worker,server]" +vidxp init +vidxp prepare +vidxp-api +``` + +The default API address is `http://127.0.0.1:32191`. Desktop may choose a +different available local port; always use the address it displays. + +## Build and load the preview + +Install Node.js 22.19, 24.15, or 26 and Adobe UXP Developer Tool 2.2 or newer. +From the repository root, run: + +```bash +npm --prefix premiere ci +npm --prefix premiere run build +``` + +In UXP Developer Tool, add `premiere/dist/manifest.json`, load the plugin, then +open **Window > UXP Plugins > VidXP Search** in Premiere Pro. + +Adobe's supported distribution format is a `.ccx` package installed through +Creative Cloud Desktop. Packaging and signing are intentionally deferred until +the preview passes the host checklist across supported Premiere versions. + +## Index Premiere media + +1. Open a Premiere project and connect the panel to the VidXP API address. +2. Search the project tree or choose **Use current selection** to mirror the + Project panel selection. +3. Select clips or bins. A bin expands to its file-backed descendants; Premiere + bins are logical containers and do not imply a filesystem directory. +4. Choose the indexing features reported by VidXP and start indexing. +5. Keep the panel open while it reports per-batch progress. A dismissible + notification reports completion or individual failures. + +Offline clips, sequences, generated items without a media path, and duplicate +source paths are not submitted. Selections larger than ten files are divided +into durable VidXP ingestion sessions of ten files each. + +## Search indexed moments + +Enter a description, choose one indexed video or the complete active library, +and select any searchable features reported by VidXP. Results show the source +video, time range, contributing features, and fused score. + +The proof of concept does not yet move Premiere's playhead, open a result in the +Source Monitor, create timeline markers, or insert generated snippets. Those +operations remain behind the Premiere adapter so they can be added without +changing the VidXP client or search UI contracts. + +## Current compatibility limits + +- Adobe documents Premiere UXP as available from Premiere Pro 25.6 with + manifest v5 and UXP Developer Tool 2.2. +- Adobe documents `getMediaFilePath()` for file-backed project items, but proxy, + subclip, Productions, UNC, and offline-media behavior still needs host + coverage. +- Adobe restricts ordinary `http://` network access on macOS. The loopback + transport in this preview must be validated before macOS support is claimed. +- React 19, built-in Spectrum control events and values, panel resize behavior, + network permissions with Desktop's dynamic port, and UXP request-origin + behavior remain manual release gates. +- Completion appears inside the panel. UXP does not expose a dependable native + Premiere toast API, and the preview does not rely on modal `alert()` calls. + +Contributors should read the [extension architecture](../../premiere/README.md) +and run the [manual host checklist](../../premiere/docs/MANUAL_TEST_CHECKLIST.md). + +## Adobe references + +- [Premiere UXP introduction](https://developer.adobe.com/premiere-pro/uxp/introduction/) +- [Premiere UXP manifest](https://developer.adobe.com/premiere-pro/uxp/plugins/concepts/manifest/) +- [Premiere network operations](https://developer.adobe.com/premiere-pro/uxp/resources/recipes/network/) +- [ClipProjectItem API](https://developer.adobe.com/premiere-pro/uxp/ppro-reference/classes/clipprojectitem) +- [Official Premiere UXP samples](https://github.com/AdobeDocs/uxp-premiere-pro-samples) +- [Package and install a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/package/) diff --git a/docs/local-api.md b/docs/local-api.md index 9c0a26c..b48aed5 100644 --- a/docs/local-api.md +++ b/docs/local-api.md @@ -52,6 +52,24 @@ If port `32191` is already in use, choose another one: vidxp-api --port 32192 ``` +### Ingest media already available on this computer + +A local application can register media without copying the video through an +HTTP request. Send one to ten absolute file paths to +`POST /api/v1/media/local-ingestions`, then poll the URL from its `Location` +header until the session is terminal. Set `modalities` to the indexable names +returned by `GET /api/v1/capabilities`; omit it to use every enabled indexing +feature. + +This operation is available only when `vidxp-api` runs in local mode. Every +path must be readable by the VidXP process and, when trusted import roots are +configured, must be inside one of those roots. The response never returns a +source path. Use the upload operations instead when the application and VidXP +do not share a filesystem. + +The [Premiere Pro extension preview](integrations/premiere-pro.md) uses this +workflow for media already loaded in a Premiere project. + ## Connect a local AI assistant An assistant that can start a program on the same computer does not need the diff --git a/premiere/README.md b/premiere/README.md new file mode 100644 index 0000000..32d7609 --- /dev/null +++ b/premiere/README.md @@ -0,0 +1,71 @@ +# VidXP Premiere Pro extension + +This directory contains the React and TypeScript UXP panel for using VidXP +inside Premiere Pro. It is independent of the Uplift product; the local Uplift +repository was used only as a packaging and host-boundary reference. + +## Architecture + +```text +React workflow (`src/ui`) +├── typed VidXP HTTP client (`src/services/vidxp`) +└── Premiere UXP adapter (`src/premiere`) + ├── project and sequence context + ├── recursive bin and clip discovery + └── Project panel selection and source-path resolution +``` + +Only the Premiere adapter imports the host-provided `premierepro` module. The +VidXP client uses HTTP contracts and has no host knowledge. The UI coordinates +selection, ingestion-session polling, and search-job polling without owning +either implementation. + +Standard buttons, text fields, text areas, and checkboxes use the Spectrum UXP +widgets built into Premiere. A small typed React adapter owns native event +listeners, boolean attributes, and controlled values. The panel does not import +Spectrum Web Components or enable the SWC manifest flag. Application-specific +layout, media-tree rows, notices, and result cards remain local CSS. Those +custom surfaces follow Premiere's current theme through `document.theme`; +Premiere does not expose the Photoshop-style `--uxp-host-*` variables. + +Capability names, descriptions, indexing support, and searchable roles come +from `GET /api/v1/capabilities`. The panel intentionally contains no mapping +for dialogue, sound, scene, action, or future capabilities. + +## Development + +```bash +npm ci +npm run check +``` + +The build writes the loadable UXP bundle to `dist/`. Add +`dist/manifest.json` to UXP Developer Tool; do not add the source manifest. +Vite keeps `premierepro`, `uxp`, and `os` external because Premiere provides +them at runtime. + +Automated tests cover the VidXP client and pure media-library selection rules. +They do not exercise Premiere, UXP, the filesystem, a running VidXP service, or +model inference. Run the [manual checklist](docs/MANUAL_TEST_CHECKLIST.md) for +those boundaries. + +## POC decisions + +- The panel passes file paths returned by Premiere to VidXP's local ingestion + endpoint. It does not request broad UXP filesystem access or copy video bytes. +- VidXP Desktop remains the setup and service-launch surface. The extension is + separately packageable and does not embed or start a Python runtime. +- Bins expand recursively and source paths are deduplicated before submission. +- React remains at version 19. Built-in Spectrum event delivery and controlled + values are release gates in the manual Premiere checklist. +- The bearer-token password input stays native because Adobe documents a macOS + value-reading bug in the built-in Spectrum password field. The media scope + also stays a native select, matching Adobe's current Premiere sample and + avoiding unnecessary dropdown-index synchronization. +- In-panel progress and completion notices are the primary status UI. +- Timeline navigation and snippet insertion remain future adapter methods. +- Plain loopback HTTP is a Windows POC transport. Cross-platform local transport + remains a packaging decision because Adobe restricts HTTP on macOS. + +The corresponding user setup is in +[`docs/integrations/premiere-pro.md`](../docs/integrations/premiere-pro.md). diff --git a/premiere/docs/MANUAL_TEST_CHECKLIST.md b/premiere/docs/MANUAL_TEST_CHECKLIST.md new file mode 100644 index 0000000..3def2a8 --- /dev/null +++ b/premiere/docs/MANUAL_TEST_CHECKLIST.md @@ -0,0 +1,77 @@ +# Premiere Pro manual test checklist + +Automated Node tests do not exercise Premiere, UXP, media paths, a real VidXP +process, or model inference. Complete this checklist in Premiere Pro 25.6 or +newer with UXP Developer Tool 2.2 or newer before claiming host support. + +## Load and layout + +- Run `npm run check` from `premiere/`. +- Add `premiere/dist/manifest.json` to UXP Developer Tool and load the plugin. +- Open **Window > UXP Plugins > VidXP Search**. +- Dock, float, resize, close, and reopen the panel in supported Premiere themes. +- Confirm built-in Spectrum buttons, text fields, text areas, and checkboxes + render with the active Premiere theme. +- Confirm Tab, Space, and Enter work as expected and focus remains visible. +- Confirm controlled values do not reset and actions fire only once after UDT + reloads and React Strict Mode remounts. +- Confirm the native bearer-token password input and media-scope selector remain + usable on Windows and macOS. +- Confirm scrolling, disclosure details, and the 320-pixel minimum layout remain + usable without CSS Grid. + +## Premiere media discovery + +- Open a project containing nested bins, online clips, offline clips, a + sequence, a subclip, a proxy, and duplicate project items for one source. +- Confirm refresh shows the active project and sequence. +- Confirm nested bins and file-backed media appear without sequences. +- Confirm offline and pathless media cannot be selected. +- Select clips and bins in the panel and confirm the count is deduplicated. +- Select clips and bins in Premiere's Project panel, choose + **Use current selection**, and confirm the same media becomes selected. +- Repeat with a Windows UNC path and, on macOS, a mounted-volume path. + +## VidXP connection + +- In Desktop, start local processing and the app integration service locally. +- Copy Desktop's dynamic API address into the extension and connect. +- Confirm the panel shows the capabilities and indexed media reported by that + runtime, including any capability added after this extension was built. +- Stop the service and confirm the panel reports a safe, actionable error. +- Try a wrong bearer token against shared mode and confirm the token is not + shown in the error or console. + +## Ingestion and status + +- Index one short real video and confirm registration, real model inference, + active-snapshot update, and the completion notice. +- Confirm the indexing action disables while work is active and enables again + after success or failure. +- Index more than ten selected clips and confirm the panel progresses through + multiple batches without duplicate media paths. +- Include one missing, unsupported, or disallowed source and confirm the other + files can finish while the failed item is identified. +- Confirm Premiere remains responsive and the panel reports meaningful progress + during a long index. +- Close and reopen the panel during a job. Record the current POC behavior; job + recovery across panel reload is not yet implemented. + +## Search + +- Search all indexed media with each searchable capability individually and in + combination. +- Search one selected VidXP media item and confirm every result belongs to it. +- Confirm each result shows the correct source name, time range, capability + labels, rank, and score. +- Search for no-match text, stop the worker, and use an unprepared capability; + confirm empty, unavailable, and model-remediation states are readable. + +## Safety and cleanup + +- Confirm no source video is copied into `premiere/`, UXP plugin data, or UXP + temporary storage. +- Confirm no API token, source path, media file, index, build output, or local + UXP Developer Tool setting appears in `git status`. +- Stop services started for the test and remove the plugin from UXP Developer + Tool when finished. diff --git a/premiere/eslint.config.mjs b/premiere/eslint.config.mjs new file mode 100644 index 0000000..3b812bc --- /dev/null +++ b/premiere/eslint.config.mjs @@ -0,0 +1,30 @@ +import eslint from "@eslint/js"; +import premierepro from "@adobe/eslint-plugin-premierepro"; +import reactHooks from "eslint-plugin-react-hooks"; +import { defineConfig, globalIgnores } from "eslint/config"; +import typescript from "typescript-eslint"; + +export default defineConfig( + globalIgnores(["dist/**", "coverage/**", "vite.config.mjs"]), + { + files: ["index.tsx", "src/**/*.{ts,tsx}", "tests/**/*.ts"], + extends: [ + eslint.configs.recommended, + ...typescript.configs.recommendedTypeChecked, + premierepro.configs.recommendedTypeChecked, + reactHooks.configs.flat.recommended, + ], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ["tests/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + }, + }, +); diff --git a/premiere/index.tsx b/premiere/index.tsx new file mode 100644 index 0000000..6c1d902 --- /dev/null +++ b/premiere/index.tsx @@ -0,0 +1,35 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import { App } from "./src/ui/App"; + +// UXP supplies this module inside Premiere at runtime. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const { entrypoints } = require("uxp") as typeof import("uxp"); + +const rootElement = document.getElementById("root"); + +if (!rootElement) { + throw new Error("The VidXP panel root element was not found."); +} + +createRoot(rootElement).render( + + + , +); + +entrypoints.setup({ + panels: { + // @ts-expect-error Adobe currently declares panels as an array even though + // runtime panel entrypoints are keyed by their manifest ID. + vidxpSearch: { + show() { + // React owns the panel DOM for the plugin context lifetime. + }, + hide() { + // Correctness does not depend on this host lifecycle hook. + }, + }, + }, +}); diff --git a/premiere/package-lock.json b/premiere/package-lock.json new file mode 100644 index 0000000..ea4390d --- /dev/null +++ b/premiere/package-lock.json @@ -0,0 +1,4296 @@ +{ + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@adobe/cc-ext-uxp-types": "7.3.1", + "@adobe/eslint-plugin-premierepro": "26.3.0", + "@adobe/premierepro": "26.3.0", + "@eslint/js": "9.39.2", + "@types/node": "24.13.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "concurrently": "10.0.5", + "eslint": "9.39.2", + "eslint-plugin-react-hooks": "7.0.1", + "rimraf": "6.1.3", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.2", + "vitest": "4.1.11" + }, + "engines": { + "node": "^22.19.0 || ^24.15.0 || ^26.0.0", + "npm": ">=11.6.0" + } + }, + "node_modules/@adobe/cc-ext-uxp-types": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@adobe/cc-ext-uxp-types/-/cc-ext-uxp-types-7.3.1.tgz", + "integrity": "sha512-HLWYoqvDXOmVr9d7l4/hY7h+Ae6IJC1Rm8uIqUK28E8nlT36A23YUsAfnamh24LZC/Xkq5jsYNORW7wX44QEKg==", + "dev": true + }, + "node_modules/@adobe/eslint-plugin-premierepro": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@adobe/eslint-plugin-premierepro/-/eslint-plugin-premierepro-26.3.0.tgz", + "integrity": "sha512-3PhOO4aVkK8eXRDusCvCELJBSkD/WA8LoL634F4b2q9UBTKKS2ZJmsH0ooAmQwUaPRiK/w9NtwzxJkVPtkMmHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@typescript-eslint/utils": "^8.58.1" + }, + "peerDependencies": { + "@adobe/premierepro": "~26.3.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.0.0", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/parser": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@adobe/premierepro": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@adobe/premierepro/-/premierepro-26.3.0.tgz", + "integrity": "sha512-J84zEX8R4L5EU5EVVs3AWkd4LRoXPKueo28jPNfwwDAo69TOSNsAblImtsAmJR4HGDDazsvHkMQE3JBJqIcB9Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", + "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", + "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", + "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", + "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", + "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", + "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", + "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", + "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", + "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", + "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", + "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", + "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", + "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", + "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", + "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", + "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", + "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.68.0", + "@typescript-eslint/types": "^8.68.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", + "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", + "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", + "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", + "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.68.0", + "@typescript-eslint/tsconfig-utils": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.68.0.tgz", + "integrity": "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", + "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", + "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", + "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "10.2.2", + "tree-kill": "1.2.2", + "yargs": "18.0.0" + }, + "bin": { + "conc": "dist/bin/index.js", + "concurrently": "dist/bin/index.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.415", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", + "integrity": "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", + "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.147.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.6", + "@rolldown/binding-android-arm64": "1.2.6", + "@rolldown/binding-darwin-arm64": "1.2.6", + "@rolldown/binding-darwin-x64": "1.2.6", + "@rolldown/binding-freebsd-x64": "1.2.6", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", + "@rolldown/binding-linux-arm64-gnu": "1.2.6", + "@rolldown/binding-linux-arm64-musl": "1.2.6", + "@rolldown/binding-linux-ppc64-gnu": "1.2.6", + "@rolldown/binding-linux-s390x-gnu": "1.2.6", + "@rolldown/binding-linux-x64-gnu": "1.2.6", + "@rolldown/binding-linux-x64-musl": "1.2.6", + "@rolldown/binding-openharmony-arm64": "1.2.6", + "@rolldown/binding-win32-arm64-msvc": "1.2.6", + "@rolldown/binding-win32-x64-msvc": "1.2.6" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.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 + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/premiere/package.json b/premiere/package.json new file mode 100644 index 0000000..0de9688 --- /dev/null +++ b/premiere/package.json @@ -0,0 +1,42 @@ +{ + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "private": true, + "type": "commonjs", + "packageManager": "npm@11.6.0", + "engines": { + "node": "^22.19.0 || ^24.15.0 || ^26.0.0", + "npm": ">=11.6.0" + }, + "scripts": { + "clean": "rimraf dist coverage", + "build": "vite build", + "dev": "concurrently -k -n build,types \"vite build --watch\" \"tsc --watch --noEmit --preserveWatchOutput\"", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc --noEmit", + "test": "vitest", + "test:run": "vitest run", + "check": "npm run typecheck && npm run lint && npm run test:run && npm run build" + }, + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@adobe/cc-ext-uxp-types": "7.3.1", + "@adobe/eslint-plugin-premierepro": "26.3.0", + "@adobe/premierepro": "26.3.0", + "@eslint/js": "9.39.2", + "@types/node": "24.13.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "concurrently": "10.0.5", + "eslint": "9.39.2", + "eslint-plugin-react-hooks": "7.0.1", + "rimraf": "6.1.3", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.2", + "vitest": "4.1.11" + } +} diff --git a/premiere/public/index.html b/premiere/public/index.html new file mode 100644 index 0000000..50c53b7 --- /dev/null +++ b/premiere/public/index.html @@ -0,0 +1,13 @@ + + + + + + VidXP Search + + + + +
+ + diff --git a/premiere/public/manifest.json b/premiere/public/manifest.json new file mode 100644 index 0000000..b8de6ac --- /dev/null +++ b/premiere/public/manifest.json @@ -0,0 +1,47 @@ +{ + "manifestVersion": 5, + "id": "org.grayhat.vidxp-premiere", + "name": "VidXP Search", + "shortname": "VidXP", + "version": "0.1.0", + "main": "index.html", + "host": { + "app": "premierepro", + "minVersion": "25.6.0" + }, + "requiredPermissions": { + "network": { + "domains": [ + "http://127.0.0.1", + "http://localhost", + "https://127.0.0.1", + "https://localhost" + ] + } + }, + "entrypoints": [ + { + "type": "panel", + "id": "vidxpSearch", + "label": { + "default": "VidXP Search" + }, + "minimumSize": { + "width": 320, + "height": 480 + }, + "maximumSize": { + "width": 1800, + "height": 1800 + }, + "preferredDockedSize": { + "width": 380, + "height": 720 + }, + "preferredFloatingSize": { + "width": 480, + "height": 760 + } + } + ] +} diff --git a/premiere/public/styles.css b/premiere/public/styles.css new file mode 100644 index 0000000..a20dc5b --- /dev/null +++ b/premiere/public/styles.css @@ -0,0 +1,446 @@ +:root { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --accent-bright: #9b88ff; + --success: #4ac58b; + --warning: #efb84d; + --danger: #f06a72; +} + +body.theme-dark { + color-scheme: dark; + --background: #191919; + --surface: #252525; + --field-background: #2d2d2d; + --border: #454545; + --text: #f4f4f4; + --muted: #aaa; +} + +body.theme-light { + color-scheme: light; + --background: #f5f5f5; + --surface: #fff; + --field-background: #fff; + --border: #b8b8b8; + --text: #242424; + --muted: #626262; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 300px; + min-height: 100vh; + background: var(--background, #191919); + color: var(--text, #f4f4f4); +} + +sp-button, +sp-textarea, +sp-textfield { + width: 100%; +} + +sp-checkbox { + flex: none; +} + +.native-secret, +.field select { + width: 100%; + padding: 8px 9px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--field-background); + color: inherit; + font: inherit; +} + +.full-width-action { + width: 100%; +} + +.inline-action, +.dismiss-action { + flex: none; + width: auto; +} + +.dismiss-action { + min-width: 26px; + font-size: 16px; +} + +.query-field { + min-height: 82px; +} + +.panel { + display: flex; + flex-direction: column; + gap: 12px; + padding: 14px; +} + +.panel-header { + padding: 4px 2px 2px; +} + +.panel-header h1 { + margin: 2px 0 0; + font-size: 22px; + letter-spacing: -.02em; +} + +.panel-header p, +.section-heading p, +.advanced p, +.status-card p, +.notice p { + margin: 4px 0 0; + color: var(--muted); + font-size: 11px; + line-height: 1.45; +} + +.eyebrow { + color: var(--accent-bright); + font-size: 10px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.section { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px; + border: 1px solid var(--border); + border-radius: 9px; + background: var(--surface); +} + +.section-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; +} + +.section-heading h2 { + margin: 0; + font-size: 13px; +} + +.field { + display: flex; + flex-direction: column; + gap: 5px; +} + +.field > span { + font-size: 11px; + font-weight: 650; +} + +.badge { + flex: none; + padding: 3px 7px; + border-radius: 999px; + background: #393939; + color: var(--muted); + font-size: 9px; + font-weight: 700; + text-transform: uppercase; +} + +.badge.ready { + background: #1e4434; + color: var(--success); +} + +.badge.error { + color: var(--danger); +} + +.badge.connecting { + color: var(--warning); +} + +.advanced { + color: var(--muted); + font-size: 11px; +} + +.advanced summary { + cursor: pointer; +} + +.advanced .field { + margin-top: 8px; +} + +.toolbar { + display: flex; + align-items: center; + gap: 7px; +} + +.toolbar > sp-textfield { + flex: 1; + min-width: 0; +} + +.library { + max-height: 220px; + overflow: auto; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: #202020; +} + +.tree-row, +.tree-bin summary { + display: flex; + align-items: center; + gap: 7px; + min-height: 27px; + padding: 4px 5px; + border-radius: 4px; + font-size: 11px; +} + +.tree-row:hover, +.tree-bin summary:hover { + background: #303030; +} + +.tree-row.disabled { + color: #777; +} + +.tree-bin summary { + cursor: pointer; + list-style: none; +} + +.tree-bin summary::-webkit-details-marker { + display: none; +} + +.tree-bin[open] > summary .node-icon { + transform: rotate(90deg); +} + +.tree-children { + margin-left: 18px; + padding-left: 3px; + border-left: 1px solid #3b3b3b; +} + +.node-icon { + width: 11px; + color: #888; + transition: transform .12s ease; +} + +.node-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.node-state { + margin-left: auto; + color: #777; + font-size: 9px; +} + +.selection-summary, +.empty { + margin: 0; + color: var(--muted); + font-size: 10px; +} + +.capabilities { + display: flex; + flex-direction: column; + gap: 5px; +} + +.capability { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 7px; + border: 1px solid #3d3d3d; + border-radius: 6px; +} + +.capability.selected { + border-color: #725fcd; + background: #302b49; +} + +.capability > span { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; + gap: 2px; + padding-top: 2px; +} + +.capability small { + color: var(--muted); + font-size: 9px; + line-height: 1.35; +} + +.status-card, +.notice { + display: flex; + align-items: flex-start; + gap: 9px; + padding: 11px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--surface); +} + +.status-card strong, +.notice strong { + font-size: 11px; +} + +.status-card.error { + border-color: var(--danger); +} + +.notice.success { + border-color: #3b9468; +} + +.notice.warning { + border-color: #a78036; +} + +.notice > div { + min-width: 0; + flex: 1; +} + +.notice p { + overflow-wrap: anywhere; +} + +.spinner { + width: 13px; + height: 13px; + flex: none; + margin-top: 1px; + border: 2px solid #555; + border-top-color: var(--accent-bright); + border-radius: 50%; + animation: spin .8s linear infinite; +} + +.status-dot { + width: 8px; + height: 8px; + flex: none; + margin-top: 3px; + border-radius: 50%; + background: var(--danger); +} + +.inline-error { + margin: 0; + color: var(--danger); + font-size: 10px; + line-height: 1.4; +} + +.results ol { + display: flex; + flex-direction: column; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.results li { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 8px; + border: 1px solid #3d3d3d; + border-radius: 6px; +} + +.result-rank { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + flex: none; + border-radius: 5px; + background: #373737; + color: var(--muted); + font-size: 10px; +} + +.result-copy { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; + gap: 3px; +} + +.result-copy > strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; +} + +.result-copy > span, +.score { + color: var(--muted); + font-size: 10px; + font-variant-numeric: tabular-nums; +} + +.score { + flex: none; +} + +.result-tags { + display: flex; + flex-wrap: wrap; + gap: 3px; +} + +.result-tags span { + padding: 2px 5px; + border-radius: 999px; + background: #383838; + color: #c9c9c9; + font-size: 8px; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/premiere/src/premiere/adapter.ts b/premiere/src/premiere/adapter.ts new file mode 100644 index 0000000..dd6a342 --- /dev/null +++ b/premiere/src/premiere/adapter.ts @@ -0,0 +1,104 @@ +import type { + FolderItem, + premierepro, + ProjectItem, +} from "@adobe/premierepro"; + +import type { + PremiereAdapter, + PremiereClip, + PremiereLibrary, + PremiereMediaNode, +} from "./types"; + +// Premiere supplies this module at runtime and Vite leaves it external. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const ppro = require("premierepro") as premierepro; + +export function createPremiereAdapter(): PremiereAdapter { + return { + async getLibrary(): Promise { + const project = await ppro.Project.getActiveProject(); + if (!project) return undefined; + + const [sequence, root] = await Promise.all([ + project.getActiveSequence(), + project.getRootItem(), + ]); + const items = await readFolder(root); + + return { + projectName: project.name, + sequenceName: sequence?.name, + items, + }; + }, + + async getSelectedProjectItemIds(): Promise { + const project = await ppro.Project.getActiveProject(); + if (!project) return []; + const selection = await ppro.ProjectUtils.getSelection(project); + const items = await selection.getItems(); + return items.map((item) => item.getId()); + }, + }; +} + +async function readFolder(folder: FolderItem): Promise { + const children = await folder.getItems(); + const nodes = await Promise.all(children.map(readProjectItem)); + return nodes.filter((node): node is PremiereMediaNode => node !== undefined); +} + +async function readProjectItem( + item: ProjectItem, +): Promise { + const id = item.getId(); + if ( + item.type === ppro.ProjectItem.TYPE_BIN || + item.type === ppro.ProjectItem.TYPE_ROOT + ) { + const folder = ppro.FolderItem.cast(item); + return { + kind: "bin", + id, + name: item.name, + children: await readFolder(folder), + }; + } + + try { + const clip = ppro.ClipProjectItem.cast(item); + if (await clip.isSequence()) return undefined; + if (await clip.isOffline()) { + return unavailableClip(id, item.name, "offline", "Media is offline in Premiere."); + } + const nativePath = (await clip.getMediaFilePath()).trim(); + if (!nativePath) { + return unavailableClip( + id, + item.name, + "unavailable", + "Premiere did not return a file-backed media path.", + ); + } + return { + kind: "clip", + id, + name: item.name, + nativePath, + availability: "ready", + }; + } catch { + return undefined; + } +} + +function unavailableClip( + id: string, + name: string, + availability: PremiereClip["availability"], + detail: string, +): PremiereClip { + return { kind: "clip", id, name, availability, detail }; +} diff --git a/premiere/src/premiere/library.ts b/premiere/src/premiere/library.ts new file mode 100644 index 0000000..a55fdb3 --- /dev/null +++ b/premiere/src/premiere/library.ts @@ -0,0 +1,69 @@ +import type { + PremiereBin, + PremiereClip, + PremiereMediaNode, +} from "./types"; + +export function collectSelectedClips( + items: PremiereMediaNode[], + selectedIds: ReadonlySet, +): PremiereClip[] { + const clips = new Map(); + + function visit(node: PremiereMediaNode, ancestorSelected: boolean) { + const selected = ancestorSelected || selectedIds.has(node.id); + if (node.kind === "bin") { + node.children.forEach((child) => visit(child, selected)); + return; + } + if (selected && node.availability === "ready" && node.nativePath) { + clips.set(node.nativePath, node); + } + } + + items.forEach((item) => visit(item, false)); + return [...clips.values()]; +} + +export function countReadyClips(items: PremiereMediaNode[]): number { + return items.reduce( + (total, item) => + total + + (item.kind === "bin" + ? countReadyClips(item.children) + : item.availability === "ready" + ? 1 + : 0), + 0, + ); +} + +export function filterLibrary( + items: PremiereMediaNode[], + rawQuery: string, +): PremiereMediaNode[] { + const query = rawQuery.trim().toLocaleLowerCase(); + if (!query) return items; + + return items.flatMap((node): PremiereMediaNode[] => { + if (node.kind === "clip") { + const searchable = `${node.name} ${node.nativePath ?? ""}`.toLocaleLowerCase(); + return searchable.includes(query) ? [node] : []; + } + const children = filterLibrary(node.children, query); + return node.name.toLocaleLowerCase().includes(query) || children.length > 0 + ? [{ ...node, children } satisfies PremiereBin] + : []; + }); +} + +export function chunkPaths(paths: string[], maximum = 10): string[][] { + if (!Number.isInteger(maximum) || maximum < 1) { + throw new Error("The ingestion batch size must be a positive integer."); + } + const batches: string[][] = []; + for (let index = 0; index < paths.length; index += maximum) { + batches.push(paths.slice(index, index + maximum)); + } + return batches; +} diff --git a/premiere/src/premiere/types.ts b/premiere/src/premiere/types.ts new file mode 100644 index 0000000..8a9b3f2 --- /dev/null +++ b/premiere/src/premiere/types.ts @@ -0,0 +1,28 @@ +export interface PremiereLibrary { + projectName: string; + sequenceName?: string; + items: PremiereMediaNode[]; +} + +export type PremiereMediaNode = PremiereBin | PremiereClip; + +export interface PremiereBin { + kind: "bin"; + id: string; + name: string; + children: PremiereMediaNode[]; +} + +export interface PremiereClip { + kind: "clip"; + id: string; + name: string; + nativePath?: string; + availability: "ready" | "offline" | "unavailable"; + detail?: string; +} + +export interface PremiereAdapter { + getLibrary(): Promise; + getSelectedProjectItemIds(): Promise; +} diff --git a/premiere/src/services/vidxp/client.ts b/premiere/src/services/vidxp/client.ts new file mode 100644 index 0000000..bbcdc45 --- /dev/null +++ b/premiere/src/services/vidxp/client.ts @@ -0,0 +1,226 @@ +import type { + CapabilitySummary, + MediaIngestionStatus, + VidXPJob, + WorkspaceOverview, +} from "./types"; + +export type VidXPFetch = typeof fetch; +type Sleep = (milliseconds: number) => Promise; + +export interface VidXPClientOptions { + baseUrl: string; + bearerToken?: string; + fetchImpl?: VidXPFetch; + sleep?: Sleep; +} + +export interface SearchRequest { + query: string; + modalities: string[]; + mediaId?: string; + topK?: number; +} + +export class VidXPApiError extends Error { + constructor( + message: string, + readonly status: number, + readonly code?: string, + ) { + super(message); + this.name = "VidXPApiError"; + } +} + +export class VidXPClient { + private readonly baseUrl: string; + private readonly bearerToken?: string; + private readonly fetchImpl: VidXPFetch; + private readonly sleep: Sleep; + + constructor(options: VidXPClientOptions) { + this.baseUrl = normalizeBaseUrl(options.baseUrl); + this.bearerToken = options.bearerToken?.trim() || undefined; + this.fetchImpl = options.fetchImpl ?? fetch; + this.sleep = options.sleep ?? delay; + } + + async health(): Promise { + await this.request<{ status: string }>("/health"); + } + + async listCapabilities(): Promise { + const response = await this.request<{ items: CapabilitySummary[] }>( + "/api/v1/capabilities", + ); + return response.items; + } + + workspace(): Promise { + return this.request("/api/v1/workspace?page_size=100"); + } + + createLocalIngestion( + paths: string[], + modalities: string[], + idempotencyKey: string, + ): Promise { + return this.request( + "/api/v1/media/local-ingestions", + { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify({ + paths, + modalities, + index_after_import: true, + }), + }, + ); + } + + getLocalIngestion(ingestionId: string): Promise { + return this.request( + `/api/v1/media/local-ingestions/${encodeURIComponent(ingestionId)}`, + ); + } + + async waitForLocalIngestion( + initial: MediaIngestionStatus, + onProgress: (status: MediaIngestionStatus) => void, + signal?: AbortSignal, + ): Promise { + let status = initial; + while (!status.terminal) { + onProgress(status); + await this.wait(status.poll_after_seconds, signal); + status = await this.getLocalIngestion(status.session_id); + } + onProgress(status); + return status; + } + + submitSearch(request: SearchRequest, idempotencyKey: string): Promise { + const query = request.query.trim(); + if (!query) throw new Error("Enter something to search for."); + return this.request("/api/v1/jobs/search", { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify({ + query, + modalities: request.modalities, + media_id: request.mediaId || null, + top_k: request.topK ?? 20, + }), + }); + } + + getJob(jobId: string): Promise { + return this.request(`/api/v1/jobs/${encodeURIComponent(jobId)}`); + } + + async waitForJob( + initial: VidXPJob, + onProgress: (job: VidXPJob) => void, + signal?: AbortSignal, + ): Promise { + let job = initial; + while (!job.terminal) { + onProgress(job); + await this.wait(job.poll_after_seconds, signal); + job = await this.getJob(job.job_id); + } + onProgress(job); + if (job.state !== "succeeded") { + throw new VidXPApiError( + job.error?.message ?? "The VidXP job did not complete.", + 409, + job.error?.code, + ); + } + return job; + } + + private async wait(seconds: number, signal?: AbortSignal): Promise { + if (signal?.aborted) throw abortError(); + await this.sleep(Math.max(250, Math.min(10_000, seconds * 1000))); + if (signal?.aborted) throw abortError(); + } + + private async request(path: string, init: RequestInit = {}): Promise { + const headers = new Headers(init.headers); + headers.set("Accept", "application/json"); + if (init.body !== undefined) headers.set("Content-Type", "application/json"); + if (this.bearerToken) { + headers.set("Authorization", `Bearer ${this.bearerToken}`); + } + const response = await this.fetchImpl(`${this.baseUrl}${path}`, { + ...init, + headers, + }); + if (!response.ok) throw await apiError(response); + return (await response.json()) as T; + } +} + +export function createIdempotencyKey(): string { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + const timestamp = Date.now().toString(16).padStart(16, "0"); + const random = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + .toString(16) + .padStart(16, "0"); + return `${timestamp}${random}`; +} + +function normalizeBaseUrl(value: string): string { + const candidate = value.trim(); + if (!candidate) throw new Error("Enter the VidXP API address shown by Desktop."); + let parsed: URL; + try { + parsed = new URL(candidate); + } catch { + throw new Error("Enter a complete VidXP API address, including http:// or https://."); + } + if (!['http:', 'https:'].includes(parsed.protocol)) { + throw new Error("The VidXP API address must use HTTP or HTTPS."); + } + parsed.pathname = ""; + parsed.search = ""; + parsed.hash = ""; + return parsed.toString().replace(/\/$/, ""); +} + +async function apiError(response: Response): Promise { + let message = `VidXP returned HTTP ${response.status}.`; + let code: string | undefined; + try { + const payload = (await response.json()) as { + error?: { + code?: string; + message?: string; + details?: { remediation?: string }; + }; + }; + if (payload.error?.message) message = payload.error.message; + if (payload.error?.details?.remediation) { + message += ` ${payload.error.details.remediation}`; + } + code = payload.error?.code; + } catch { + // Keep the bounded status-only fallback; response bodies may contain internals. + } + return new VidXPApiError(message, response.status, code); +} + +function delay(milliseconds: number): Promise { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +function abortError(): Error { + const error = new Error("Operation cancelled"); + error.name = "AbortError"; + return error; +} diff --git a/premiere/src/services/vidxp/types.ts b/premiere/src/services/vidxp/types.ts new file mode 100644 index 0000000..e85a39a --- /dev/null +++ b/premiere/src/services/vidxp/types.ts @@ -0,0 +1,112 @@ +export interface CapabilitySummary { + name: string; + description: string; + supports_indexing: boolean; + prepares_models: boolean; + roles: string[]; +} + +export interface WorkspaceMedia { + media_id: string; + original_filename: string; + duration_seconds?: number; + state: string; + in_active_snapshot: boolean; +} + +export interface WorkspaceOverview { + capabilities: CapabilitySummary[]; + media: WorkspaceMedia[]; + media_total: number; + next_cursor?: string; + index: { + state: string; + stage: string; + message: string; + summary?: { + media_count: number; + modalities: string[]; + }; + }; + next_actions: string[]; +} + +export interface ErrorDetail { + code: string; + message: string; + retryable?: boolean; + details?: { + remediation?: string; + }; +} + +export interface MediaUploadStatus { + intent_id: string; + original_filename: string; + phase: string; + media_id?: string; + searchable: boolean; + terminal: boolean; + status: string; + error?: ErrorDetail; +} + +export interface MediaIngestionStatus { + session_id: string; + aggregate_state: string; + index_modalities: string[]; + file_count: number; + searchable_file_count: number; + failed_file_count: number; + index_failed_file_count: number; + items: MediaUploadStatus[]; + terminal: boolean; + poll_after_seconds: number; + status: string; + next_action: string; +} + +export interface JobProgress { + stage: string; + message: string; + current?: number; + total?: number; +} + +export interface SearchHit { + modality: string; + score: number; + metadata: Record; +} + +export interface FusedMoment { + moment_id?: string; + rank: number; + score: number; + media_id: string; + start: number; + end: number; + modalities: string[]; + hits: SearchHit[]; +} + +export interface FusedSearchResult { + query_id: string; + query: string; + modalities: string[]; + moments: FusedMoment[]; +} + +export interface VidXPJob { + job_id: string; + kind: string; + state: string; + progress?: JobProgress; + result?: { + kind: string; + result: FusedSearchResult; + }; + error?: ErrorDetail; + terminal: boolean; + poll_after_seconds: number; +} diff --git a/premiere/src/ui/App.tsx b/premiere/src/ui/App.tsx new file mode 100644 index 0000000..9b80cf0 --- /dev/null +++ b/premiere/src/ui/App.tsx @@ -0,0 +1,644 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +import { createPremiereAdapter } from "../premiere/adapter"; +import { + chunkPaths, + collectSelectedClips, + countReadyClips, + filterLibrary, +} from "../premiere/library"; +import type { PremiereLibrary, PremiereMediaNode } from "../premiere/types"; +import { + createIdempotencyKey, + VidXPClient, +} from "../services/vidxp/client"; +import type { + CapabilitySummary, + FusedMoment, + WorkspaceOverview, +} from "../services/vidxp/types"; +import { + SpectrumActionButton, + SpectrumButton, + SpectrumCheckbox, + SpectrumTextArea, + SpectrumTextField, +} from "./components/Spectrum"; +import { installPremiereTheme } from "./theme"; + +type ConnectionState = + | { status: "disconnected" } + | { status: "connecting" } + | { status: "ready" } + | { status: "error"; message: string }; + +type OperationState = + | { status: "idle" } + | { status: "indexing" | "searching"; message: string } + | { status: "error"; message: string }; + +interface Notice { + tone: "success" | "warning"; + title: string; + message: string; +} + +const DEFAULT_API_ADDRESS = "http://127.0.0.1:32191"; + +export function App() { + const premiere = useMemo(() => createPremiereAdapter(), []); + const abortController = useRef(undefined); + const [apiAddress, setApiAddress] = useState(DEFAULT_API_ADDRESS); + const [bearerToken, setBearerToken] = useState(""); + const [client, setClient] = useState(); + const [connection, setConnection] = useState({ + status: "disconnected", + }); + const [library, setLibrary] = useState(); + const [libraryError, setLibraryError] = useState(); + const [libraryFilter, setLibraryFilter] = useState(""); + const [selectedIds, setSelectedIds] = useState([]); + const [capabilities, setCapabilities] = useState([]); + const [indexModalities, setIndexModalities] = useState([]); + const [searchModalities, setSearchModalities] = useState([]); + const [workspace, setWorkspace] = useState(); + const [operation, setOperation] = useState({ status: "idle" }); + const [notice, setNotice] = useState(); + const [query, setQuery] = useState(""); + const [mediaScope, setMediaScope] = useState(""); + const [moments, setMoments] = useState([]); + + const loadLibrary = useCallback(async () => { + setLibraryError(undefined); + try { + const nextLibrary = await premiere.getLibrary(); + setLibrary(nextLibrary); + if (!nextLibrary) setSelectedIds([]); + } catch (error) { + setLibrary(undefined); + setLibraryError(messageOf(error)); + } + }, [premiere]); + + useEffect(() => { + void loadLibrary(); + return () => abortController.current?.abort(); + }, [loadLibrary]); + + useEffect(() => installPremiereTheme(document), []); + + const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]); + const selectedClips = useMemo( + () => collectSelectedClips(library?.items ?? [], selectedSet), + [library, selectedSet], + ); + const visibleLibrary = useMemo( + () => filterLibrary(library?.items ?? [], libraryFilter), + [library, libraryFilter], + ); + const indexableCapabilities = capabilities.filter( + (capability) => capability.supports_indexing, + ); + const searchableCapabilities = capabilities.filter((capability) => + capability.roles.includes("searchable"), + ); + const busy = operation.status === "indexing" || operation.status === "searching"; + + async function connect() { + setConnection({ status: "connecting" }); + setNotice(undefined); + try { + const nextClient = new VidXPClient({ + baseUrl: apiAddress, + bearerToken, + }); + const [, nextCapabilities, nextWorkspace] = await Promise.all([ + nextClient.health(), + nextClient.listCapabilities(), + nextClient.workspace(), + ]); + setClient(nextClient); + setCapabilities(nextCapabilities); + setIndexModalities( + nextCapabilities + .filter((capability) => capability.supports_indexing) + .map((capability) => capability.name), + ); + setSearchModalities( + nextCapabilities + .filter((capability) => capability.roles.includes("searchable")) + .map((capability) => capability.name), + ); + setWorkspace(nextWorkspace); + setConnection({ status: "ready" }); + } catch (error) { + setClient(undefined); + setConnection({ status: "error", message: messageOf(error) }); + } + } + + async function selectCurrentPremiereItems() { + try { + setSelectedIds(await premiere.getSelectedProjectItemIds()); + } catch (error) { + setLibraryError(messageOf(error)); + } + } + + async function indexSelection() { + if (!client || selectedClips.length === 0 || indexModalities.length === 0) return; + const controller = beginOperation(); + setNotice(undefined); + const batches = chunkPaths(selectedClips.map((clip) => clip.nativePath!)); + let searchable = 0; + const failures: string[] = []; + + try { + for (const [batchIndex, paths] of batches.entries()) { + const initial = await client.createLocalIngestion( + paths, + indexModalities, + createIdempotencyKey(), + ); + const final = await client.waitForLocalIngestion( + initial, + (status) => { + setOperation({ + status: "indexing", + message: `Batch ${batchIndex + 1} of ${batches.length} · ${status.status}`, + }); + }, + controller.signal, + ); + searchable += final.searchable_file_count; + failures.push( + ...final.items + .filter((item) => item.error) + .map( + (item) => + `${item.original_filename}: ${item.error?.message ?? "Indexing failed."}`, + ), + ); + } + setWorkspace(await client.workspace()); + setNotice({ + tone: failures.length > 0 ? "warning" : "success", + title: failures.length > 0 ? "Indexing finished with issues" : "Indexing complete", + message: + failures.length > 0 + ? `${searchable} media item(s) are searchable. ${failures.join(" ")}` + : `${searchable} media item(s) from Premiere are now searchable.`, + }); + setOperation({ status: "idle" }); + } catch (error) { + if (!controller.signal.aborted) { + setOperation({ status: "error", message: messageOf(error) }); + } + } + } + + async function search() { + if (!client || !query.trim()) return; + const controller = beginOperation(); + setNotice(undefined); + try { + const initial = await client.submitSearch( + { + query, + modalities: searchModalities, + mediaId: mediaScope || undefined, + topK: 20, + }, + createIdempotencyKey(), + ); + const completed = await client.waitForJob( + initial, + (job) => { + setOperation({ + status: "searching", + message: job.progress?.message ?? "VidXP is searching indexed media…", + }); + }, + controller.signal, + ); + const result = completed.result?.result; + if (!result) throw new Error("VidXP completed the search without a result payload."); + setMoments(result.moments); + if (result.moments.length === 0) { + setNotice({ + tone: "warning", + title: "No matching moments", + message: "Try a broader description, another search feature, or the complete indexed library.", + }); + } + setOperation({ status: "idle" }); + } catch (error) { + if (!controller.signal.aborted) { + setMoments([]); + setOperation({ status: "error", message: messageOf(error) }); + } + } + } + + function beginOperation(): AbortController { + abortController.current?.abort(); + const controller = new AbortController(); + abortController.current = controller; + return controller; + } + + function toggleSelected(id: string) { + setSelectedIds((current) => + current.includes(id) + ? current.filter((candidate) => candidate !== id) + : [...current, id], + ); + } + + return ( +
+
+
+ Premiere Pro · Preview +

VidXP Search

+

Index project media and find the moment you remember.

+
+
+ +
+
+
+

VidXP connection

+

Use the API address shown by VidXP Desktop’s app integration service.

+
+ +
+ +
+ Shared-server authentication + +

The token stays in panel memory and is not written to the Premiere project.

+
+ void connect()} + disabled={busy} + > + {connection.status === "connecting" ? "Connecting…" : "Connect"} + + {connection.status === "error" && ( +

{connection.message}

+ )} +
+ +
+
+
+

Premiere media

+

+ {library + ? `${library.projectName}${library.sequenceName ? ` · ${library.sequenceName}` : ""}` + : "Open a Premiere project to browse its bins."} +

+
+ void loadLibrary()} + > + Refresh + +
+ {libraryError &&

{libraryError}

} + {library && ( + <> +
+ + void selectCurrentPremiereItems()} + > + Use current selection + +
+
+ {visibleLibrary.length > 0 ? ( + visibleLibrary.map((node) => ( + + )) + ) : ( +

No matching media.

+ )} +
+

+ {selectedClips.length} selected · {countReadyClips(library.items)} file-backed media items available +

+ + )} +
+ +
+
+
+

Index selected media

+

VidXP expands selected bins, deduplicates clips, and indexes in batches of ten.

+
+
+ setIndexModalities(next)} + emptyMessage="Connect to VidXP to discover indexable features." + /> + void indexSelection()} + > + {operation.status === "indexing" + ? "Indexing…" + : `Index ${selectedClips.length || "selected"} media item${selectedClips.length === 1 ? "" : "s"}`} + +
+ +
+
+
+

Search moments

+

Search one indexed video or the complete active VidXP library.

+
+
+ + + setSearchModalities(next)} + emptyMessage="No searchable features are available yet." + /> + void search()} + > + {operation.status === "searching" ? "Searching…" : "Search indexed media"} + +
+ + {operation.status !== "idle" && ( +
+
+ )} + + {notice && ( +
+
+ {notice.title} +

{notice.message}

+
+ setNotice(undefined)} + > + × + +
+ )} + + +
+ ); +} + +function ConnectionBadge({ state }: { state: ConnectionState }) { + const label = + state.status === "ready" + ? "Connected" + : state.status === "connecting" + ? "Checking" + : state.status === "error" + ? "Unavailable" + : "Not connected"; + return {label}; +} + +function MediaTreeNode({ + node, + selected, + inherited, + onToggle, +}: { + node: PremiereMediaNode; + selected: ReadonlySet; + inherited: boolean; + onToggle: (id: string) => void; +}) { + const effectiveSelected = inherited || selected.has(node.id); + if (node.kind === "clip") { + const disabled = node.availability !== "ready"; + return ( +
+ onToggle(node.id)} + > + + {node.name} + {disabled && {node.availability}} +
+ ); + } + return ( +
+ + event.stopPropagation()} + onCheckedChange={() => onToggle(node.id)} + > + + {node.name} + {countReadyClips(node.children)} + +
+ {node.children.map((child) => ( + + ))} +
+
+ ); +} + +function CapabilityChoices({ + capabilities, + selected, + onChange, + emptyMessage, +}: { + capabilities: CapabilitySummary[]; + selected: string[]; + onChange: (next: string[]) => void; + emptyMessage: string; +}) { + if (capabilities.length === 0) return

{emptyMessage}

; + return ( +
+ {capabilities.map((capability) => ( +
+ + onChange( + selected.includes(capability.name) + ? selected.filter((name) => name !== capability.name) + : [...selected, capability.name], + ) + } + > + {capability.name} + + + {capability.description} + +
+ ))} +
+ ); +} + +function SearchResults({ + moments, + workspace, +}: { + moments: FusedMoment[]; + workspace?: WorkspaceOverview; +}) { + if (moments.length === 0) return null; + const names = new Map( + workspace?.media.map((media) => [media.media_id, media.original_filename]) ?? [], + ); + return ( +
+
+
+

Matching moments

+

{moments.length} ranked result{moments.length === 1 ? "" : "s"}

+
+
+
    + {moments.map((moment) => ( +
  1. +
    {moment.rank}
    +
    + {names.get(moment.media_id) ?? moment.media_id} + {formatTime(moment.start)} – {formatTime(moment.end)} +
    + {moment.modalities.map((modality) => {modality})} +
    +
    + {moment.score.toFixed(3)} +
  2. + ))} +
+
+ ); +} + +function formatTime(seconds: number): string { + const whole = Math.max(0, Math.floor(seconds)); + const hours = Math.floor(whole / 3600); + const minutes = Math.floor((whole % 3600) / 60); + const remainder = whole % 60; + return hours > 0 + ? `${hours}:${minutes.toString().padStart(2, "0")}:${remainder.toString().padStart(2, "0")}` + : `${minutes}:${remainder.toString().padStart(2, "0")}`; +} + +function messageOf(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/premiere/src/ui/components/Spectrum.tsx b/premiere/src/ui/components/Spectrum.tsx new file mode 100644 index 0000000..a40d814 --- /dev/null +++ b/premiere/src/ui/components/Spectrum.tsx @@ -0,0 +1,247 @@ +import { + useEffect, + useRef, + type ReactNode, + type RefObject, +} from "react"; + +type ButtonVariant = + | "cta" + | "primary" + | "secondary" + | "warning" + | "overBackground"; + +interface CommonControlProps { + ariaLabel?: string; + className?: string; + disabled?: boolean; +} + +interface SpectrumButtonProps extends CommonControlProps { + children: ReactNode; + onPress?: (event: Event) => void; + quiet?: boolean; + variant?: ButtonVariant; +} + +interface SpectrumActionButtonProps extends CommonControlProps { + children: ReactNode; + onPress?: (event: Event) => void; + quiet?: boolean; + selected?: boolean; +} + +interface SpectrumTextFieldProps extends CommonControlProps { + label?: string; + onValueChange: (value: string) => void; + placeholder?: string; + quiet?: boolean; + type?: "text" | "number" | "search"; + value: string; +} + +interface SpectrumTextAreaProps extends CommonControlProps { + label?: string; + onValueChange: (value: string) => void; + placeholder?: string; + quiet?: boolean; + value: string; +} + +interface SpectrumCheckboxProps extends CommonControlProps { + checked: boolean; + children?: ReactNode; + indeterminate?: boolean; + onCheckedChange: (checked: boolean) => void; + onPress?: (event: Event) => void; +} + +export function SpectrumButton(props: SpectrumButtonProps) { + return ; +} + +export function SpectrumActionButton(props: SpectrumActionButtonProps) { + return ; +} + +function SpectrumButtonElement({ + tag, + ariaLabel, + children, + className, + disabled = false, + onPress, + quiet = false, + selected = false, + variant, +}: SpectrumButtonProps & { + selected?: boolean; + tag: "sp-action-button" | "sp-button"; +}) { + const ref = useRef(null); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "quiet", quiet); + useUxpProperty(ref, "selected", selected); + useUxpEvent(ref, "click", onPress); + + return tag === "sp-button" ? ( + + {children} + + ) : ( + + {children} + + ); +} + +export function SpectrumTextField({ + ariaLabel, + className, + disabled = false, + label, + onValueChange, + placeholder, + quiet = false, + type = "text", + value, +}: SpectrumTextFieldProps) { + const ref = useRef(null); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "quiet", quiet); + useUxpProperty(ref, "value", value); + useUxpEvent(ref, "input", (event) => { + onValueChange(readEventProperty(event, "value", "")); + }); + + return ( + + {label ? {label} : undefined} + + ); +} + +export function SpectrumTextArea({ + ariaLabel, + className, + disabled = false, + label, + onValueChange, + placeholder, + quiet = false, + value, +}: SpectrumTextAreaProps) { + const ref = useRef(null); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "quiet", quiet); + useUxpProperty(ref, "value", value); + useUxpEvent(ref, "input", (event) => { + onValueChange(readEventProperty(event, "value", "")); + }); + + return ( + + {label ? {label} : undefined} + + ); +} + +export function SpectrumCheckbox({ + ariaLabel, + checked, + children, + className, + disabled = false, + indeterminate = false, + onCheckedChange, + onPress, +}: SpectrumCheckboxProps) { + const ref = useRef(null); + useUxpProperty(ref, "checked", checked); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "indeterminate", indeterminate); + useUxpEvent(ref, "change", (event) => { + onCheckedChange(readEventProperty(event, "checked", false)); + }); + useUxpEvent(ref, "click", onPress); + + return ( + + {children} + + ); +} + +function booleanAttribute(value: boolean): true | undefined { + return value ? true : undefined; +} + +function readEventProperty(event: Event, property: string, fallback: T): T { + const target = event.currentTarget; + if (!target) return fallback; + const value = Reflect.get(target, property) as T | undefined; + return value ?? fallback; +} + +function useUxpEvent( + ref: RefObject, + eventName: "change" | "click" | "input", + handler?: (event: Event) => void, +) { + useEffect(() => { + const element = ref.current; + if (!element || !handler) return; + element.addEventListener(eventName, handler); + return () => element.removeEventListener(eventName, handler); + }, [eventName, handler, ref]); +} + +function useUxpProperty( + ref: RefObject, + property: string, + value: T, +) { + useEffect(() => { + const element = ref.current; + if (element) Reflect.set(element, property, value); + }, [property, ref, value]); +} diff --git a/premiere/src/ui/spectrum-elements.d.ts b/premiere/src/ui/spectrum-elements.d.ts new file mode 100644 index 0000000..1a037de --- /dev/null +++ b/premiere/src/ui/spectrum-elements.d.ts @@ -0,0 +1,29 @@ +import type { HTMLAttributes, RefAttributes } from "react"; + +interface SpectrumElementAttributes + extends Omit, "className">, + RefAttributes { + checked?: true; + class?: string; + disabled?: true; + indeterminate?: true; + placeholder?: string; + quiet?: true; + selected?: true; + type?: string; + value?: number | string; + variant?: string; +} + +declare module "react/jsx-runtime" { + namespace JSX { + interface IntrinsicElements { + "sp-action-button": SpectrumElementAttributes; + "sp-button": SpectrumElementAttributes; + "sp-checkbox": SpectrumElementAttributes; + "sp-label": SpectrumElementAttributes; + "sp-textarea": SpectrumElementAttributes; + "sp-textfield": SpectrumElementAttributes; + } + } +} diff --git a/premiere/src/ui/theme.ts b/premiere/src/ui/theme.ts new file mode 100644 index 0000000..fc0b514 --- /dev/null +++ b/premiere/src/ui/theme.ts @@ -0,0 +1,42 @@ +export type PremiereThemeClass = "theme-dark" | "theme-light"; + +interface ThemeListenerCollection { + addListener(listener: (theme: string) => void): void; + removeListener(listener: (theme: string) => void): void; +} + +interface PremiereThemeApi { + getCurrent(): string; + onUpdated: ThemeListenerCollection; +} + +interface ThemeDocument { + body: { + classList: { + add(...tokens: string[]): void; + remove(...tokens: string[]): void; + }; + }; + theme?: PremiereThemeApi; +} + +export function premiereThemeClass(theme: string): PremiereThemeClass { + return theme.toLowerCase().includes("light") ? "theme-light" : "theme-dark"; +} + +export function installPremiereTheme(target: ThemeDocument): () => void { + const applyTheme = (theme: string) => { + target.body.classList.remove("theme-dark", "theme-light"); + target.body.classList.add(premiereThemeClass(theme)); + }; + + const theme = target.theme; + if (!theme) { + applyTheme("dark"); + return () => undefined; + } + + applyTheme(theme.getCurrent()); + theme.onUpdated.addListener(applyTheme); + return () => theme.onUpdated.removeListener(applyTheme); +} diff --git a/premiere/tests/library.test.ts b/premiere/tests/library.test.ts new file mode 100644 index 0000000..3751395 --- /dev/null +++ b/premiere/tests/library.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "vitest"; + +import { + chunkPaths, + collectSelectedClips, + filterLibrary, +} from "../src/premiere/library"; +import type { PremiereMediaNode } from "../src/premiere/types"; + +const library: PremiereMediaNode[] = [ + { + kind: "bin", + id: "interviews", + name: "Interviews", + children: [ + { + kind: "clip", + id: "clip-a", + name: "A camera.mp4", + nativePath: "C:/Media/a.mp4", + availability: "ready", + }, + { + kind: "clip", + id: "clip-offline", + name: "Offline.mov", + availability: "offline", + }, + ], + }, + { + kind: "clip", + id: "clip-b", + name: "B-roll.mov", + nativePath: "C:/Media/b.mov", + availability: "ready", + }, +]; + +describe("Premiere media library helpers", () => { + it("expands bins, skips unavailable items, and deduplicates source paths", () => { + const duplicate = { + ...library[1], + id: "duplicate-b", + }; + const clips = collectSelectedClips( + [...library, duplicate], + new Set(["interviews", "clip-b", "duplicate-b"]), + ); + + expect(clips.map((clip) => clip.nativePath)).toEqual([ + "C:/Media/a.mp4", + "C:/Media/b.mov", + ]); + }); + + it("keeps a matching bin with only matching descendants", () => { + expect(filterLibrary(library, "camera")).toEqual([ + { + kind: "bin", + id: "interviews", + name: "Interviews", + children: [library[0].kind === "bin" ? library[0].children[0] : null], + }, + ]); + }); + + it("chunks large Premiere selections for the ten-path ingestion contract", () => { + const paths = Array.from({ length: 23 }, (_, index) => `clip-${index}`); + expect(chunkPaths(paths).map((batch) => batch.length)).toEqual([10, 10, 3]); + }); +}); diff --git a/premiere/tests/theme.test.ts b/premiere/tests/theme.test.ts new file mode 100644 index 0000000..73f2a30 --- /dev/null +++ b/premiere/tests/theme.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + installPremiereTheme, + premiereThemeClass, +} from "../src/ui/theme"; + +describe("Premiere theme integration", () => { + it("maps Premiere light themes and dark variants", () => { + expect(premiereThemeClass("light")).toBe("theme-light"); + expect(premiereThemeClass("darkest")).toBe("theme-dark"); + }); + + it("applies updates and removes its listener during cleanup", () => { + const classes = new Set(); + let listener: ((theme: string) => void) | undefined; + const addListener = vi.fn((next: (theme: string) => void) => { + listener = next; + }); + const removeListener = vi.fn(); + const cleanup = installPremiereTheme({ + body: { + classList: { + add: (...tokens) => tokens.forEach((token) => classes.add(token)), + remove: (...tokens) => tokens.forEach((token) => classes.delete(token)), + }, + }, + theme: { + getCurrent: () => "dark", + onUpdated: { addListener, removeListener }, + }, + }); + + expect(classes).toEqual(new Set(["theme-dark"])); + listener?.("light"); + expect(classes).toEqual(new Set(["theme-light"])); + + cleanup(); + expect(removeListener).toHaveBeenCalledOnce(); + }); +}); diff --git a/premiere/tests/vidxp-client.test.ts b/premiere/tests/vidxp-client.test.ts new file mode 100644 index 0000000..0f8247c --- /dev/null +++ b/premiere/tests/vidxp-client.test.ts @@ -0,0 +1,124 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + VidXPApiError, + VidXPClient, + type VidXPFetch, +} from "../src/services/vidxp/client"; +import type { MediaIngestionStatus, VidXPJob } from "../src/services/vidxp/types"; + +const ingestion: MediaIngestionStatus = { + session_id: "ingestion-1", + aggregate_state: "processing", + index_modalities: ["scene"], + file_count: 1, + searchable_file_count: 0, + failed_file_count: 0, + index_failed_file_count: 0, + items: [], + terminal: false, + poll_after_seconds: 1, + status: "Importing", + next_action: "Poll", +}; + +describe("VidXPClient", () => { + it("submits Premiere paths to the local ingestion contract", async () => { + const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(ingestion)); + const client = new VidXPClient({ + baseUrl: " http://127.0.0.1:32191/docs ", + bearerToken: " local-token ", + fetchImpl: fetchImpl as VidXPFetch, + }); + + await client.createLocalIngestion( + ["C:/Media/a.mp4"], + ["scene"], + "request-key", + ); + + const [url, init] = fetchImpl.mock.calls[0] as [string, RequestInit]; + const headers = new Headers(init.headers); + expect(url).toBe("http://127.0.0.1:32191/api/v1/media/local-ingestions"); + expect(init.method).toBe("POST"); + expect(headers.get("Authorization")).toBe("Bearer local-token"); + expect(headers.get("Idempotency-Key")).toBe("request-key"); + if (typeof init.body !== "string") throw new Error("Expected a JSON request body."); + expect(JSON.parse(init.body)).toEqual({ + paths: ["C:/Media/a.mp4"], + modalities: ["scene"], + index_after_import: true, + }); + }); + + it("polls a search job and returns its typed result", async () => { + const completed: VidXPJob = { + job_id: "job-1", + kind: "search", + state: "succeeded", + terminal: true, + poll_after_seconds: 0, + result: { + kind: "search", + result: { + query_id: "query-1", + query: "door opens", + modalities: ["scene"], + moments: [], + }, + }, + }; + const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(completed)); + const sleep = vi.fn().mockResolvedValue(undefined); + const client = new VidXPClient({ + baseUrl: "http://localhost:32191", + fetchImpl: fetchImpl as VidXPFetch, + sleep, + }); + const queued: VidXPJob = { + job_id: "job-1", + kind: "search", + state: "queued", + terminal: false, + poll_after_seconds: 1, + }; + + const result = await client.waitForJob(queued, vi.fn()); + + expect(sleep).toHaveBeenCalledWith(1000); + expect(result.result?.result.query_id).toBe("query-1"); + }); + + it("surfaces safe API remediation without exposing the bearer token", async () => { + const fetchImpl = vi.fn().mockResolvedValue( + jsonResponse( + { + error: { + code: "models_unavailable", + message: "Models are not prepared.", + details: { remediation: "Prepare the selected features in Desktop." }, + }, + }, + 503, + ), + ); + const client = new VidXPClient({ + baseUrl: "http://localhost:32191", + bearerToken: "secret-token", + fetchImpl: fetchImpl as VidXPFetch, + }); + + const error = await client.listCapabilities().catch((caught: unknown) => caught); + + expect(error).toBeInstanceOf(VidXPApiError); + expect(String(error)).toContain("Prepare the selected features in Desktop."); + expect(String(error)).not.toContain("secret-token"); + }); +}); + +function jsonResponse(payload: unknown, status = 200): Response { + return new Response(JSON.stringify(payload), { + status, + headers: { "content-type": "application/json" }, + }); +} diff --git a/premiere/tsconfig.json b/premiere/tsconfig.json new file mode 100644 index 0000000..27d9894 --- /dev/null +++ b/premiere/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "types": [ + "@adobe/cc-ext-uxp-types", + "@adobe/premierepro", + "@types/node", + "vitest/globals" + ] + }, + "include": ["index.tsx", "src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/premiere/vite.config.mjs b/premiere/vite.config.mjs new file mode 100644 index 0000000..5401e66 --- /dev/null +++ b/premiere/vite.config.mjs @@ -0,0 +1,31 @@ +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vite"; + +const projectRoot = fileURLToPath(new URL(".", import.meta.url)); + +export default defineConfig({ + publicDir: "public", + base: "./", + build: { + outDir: "dist", + emptyOutDir: true, + minify: false, + sourcemap: true, + target: "esnext", + rolldownOptions: { + input: resolve(projectRoot, "index.tsx"), + external: ["os", "premierepro", "uxp"], + output: { + format: "cjs", + preserveModules: true, + preserveModulesRoot: projectRoot, + entryFileNames: "[name].js" + } + } + }, + test: { + environment: "node", + include: ["tests/**/*.test.ts"] + } +}); diff --git a/src/vidxp/api_routes/media.py b/src/vidxp/api_routes/media.py index 504af0f..4ab31bf 100644 --- a/src/vidxp/api_routes/media.py +++ b/src/vidxp/api_routes/media.py @@ -17,21 +17,39 @@ CreateUploadIntentCommand, ErrorCategory, ListMediaCommand, + LocalMediaIngestionCommand, MediaAsset, MediaPage, + MediaUploadSessionStatus, Principal, UploadIntent, UploadIntentId, + UploadSessionId, ) from vidxp.api_models import UploadIntentResponse from vidxp.composition import HttpApplicationContext from vidxp.core.identifiers import MediaId from vidxp.core.media import MediaState +from vidxp.network_share import is_loopback_host router = APIRouter(prefix="/media", tags=["media"]) +def _local_ingestion_service( + service: HttpApplicationContext, +): + if service.uploads is None or not is_loopback_host( + service.settings.http_bind_host + ): + raise ApplicationError( + "local_ingestion_unavailable", + ErrorCategory.unavailable, + "Local media ingestion is not configured for this HTTP service.", + ) + return service.uploads + + def _upload_response( service: HttpApplicationContext, actor: Principal, @@ -158,6 +176,62 @@ def import_media( staged.unlink(missing_ok=True) +@router.post( + "/local-ingestions", + response_model=MediaUploadSessionStatus, + status_code=202, + operation_id="ingestLocalMedia", + summary="Ingest local media paths", + description=( + "Local runtime only: register and optionally index up to ten media " + "paths that are available to the VidXP process. Poll the returned " + "session for per-file progress." + ), + dependencies=[Depends(write_principal)], +) +def create_local_ingestion( + command: LocalMediaIngestionCommand, + response: Response, + service: Annotated[HttpApplicationContext, Depends(context)], + actor: Annotated[Principal, Depends(write_principal)], + idempotency_key: HttpIdempotencyKey, +) -> MediaUploadSessionStatus: + uploads = _local_ingestion_service(service) + selected = service.application.select_index_modalities(command.modalities) + status = uploads.create_local_ingestion( + command.paths, + principal=actor, + request_key=scoped_request_key( + service, + actor, + operation="local-media-ingestion", + idempotency_key=idempotency_key, + ), + index_after_import=command.index_after_import, + index_modalities=selected, + ) + response.headers["Location"] = ( + f"/api/v1/media/local-ingestions/{status.session_id}" + ) + return status + + +@router.get( + "/local-ingestions/{ingestion_id}", + response_model=MediaUploadSessionStatus, + operation_id="getLocalMediaIngestion", + summary="Get local media ingestion status", + dependencies=[Depends(read_principal)], +) +def get_local_ingestion( + ingestion_id: UploadSessionId, + service: Annotated[HttpApplicationContext, Depends(context)], + actor: Annotated[Principal, Depends(read_principal)], +) -> MediaUploadSessionStatus: + uploads = _local_ingestion_service(service) + return uploads.get_status(ingestion_id, principal=actor) + + @router.get( "", response_model=MediaPage, diff --git a/src/vidxp/control_plane.py b/src/vidxp/control_plane.py index 91150bf..7b84a08 100644 --- a/src/vidxp/control_plane.py +++ b/src/vidxp/control_plane.py @@ -13,6 +13,7 @@ CreateIndexCommand, DependencyCheckResult, IndexStatus, + Identifier, InvalidRequestError, ListMediaCommand, MediaAsset, @@ -88,6 +89,33 @@ def get_capability(self, name: str) -> CapabilityInfo: except CapabilityRequestError as exc: raise ResourceNotFoundError("capability") from exc + @application_boundary + def select_index_modalities( + self, + requested: tuple[Identifier, ...] | None, + ) -> tuple[str, ...]: + """Resolve an optional capability selection to indexable names.""" + + registry = self.capabilities.registry + indexable = registry.index_names() + selected = ( + indexable + if requested is None + else registry.validate_names(requested) + ) + unsupported = tuple( + name for name in selected if name not in indexable + ) + if unsupported: + raise CapabilityRequestError( + "Indexing does not support these capabilities: " + + ", ".join(unsupported) + + ".", + field="modalities", + reason="capability_not_indexable", + ) + return selected + @application_boundary def index_status(self) -> IndexStatus: stored = self._read_index_status() diff --git a/src/vidxp/upload_service.py b/src/vidxp/upload_service.py index dee90af..dc5d21f 100644 --- a/src/vidxp/upload_service.py +++ b/src/vidxp/upload_service.py @@ -384,7 +384,7 @@ def create_local_ingestion( raise ApplicationError( "local_ingestion_unavailable", ErrorCategory.unavailable, - "Local-path ingestion is available only to the local stdio server.", + "Local-path ingestion is available only to a local VidXP runtime.", ) if self.media is None or self.jobs is None: raise ApplicationError( diff --git a/tests/test_api.py b/tests/test_api.py index 1e510ff..7bd6d73 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -40,6 +40,7 @@ IndexStatus, MediaAsset, MediaPage, + MediaUploadSessionStatus, Principal, SearchCommand, SearchJobResult, @@ -56,7 +57,11 @@ from vidxp.authorization import AuthorizationPolicy from vidxp.core.media import MediaState, MediaStream from vidxp.core.artifacts import ArtifactKind, ArtifactState -from vidxp.core.uploads import UploadState +from vidxp.core.uploads import ( + UploadSessionState, + UploadState, + UploadTransferBackend, +) from vidxp.job_service import JobService from vidxp.ports import LocalFileResource from vidxp.readiness_service import ReadinessService @@ -68,6 +73,7 @@ JOB_ID = "223456781234423481234567890abcde" IDEMPOTENCY_KEY = "323456781234423481234567890abcde" ARTIFACT_ID = "423456781234423481234567890abcde" +INGESTION_ID = "523456781234423481234567890abcde" TOKEN = "a" * 32 @@ -104,6 +110,35 @@ def queued_job() -> Job: ) +def ingestion_status() -> MediaUploadSessionStatus: + now = datetime.now(timezone.utc) + return MediaUploadSessionStatus( + session_id=INGESTION_ID, + session_state=UploadSessionState.closed, + aggregate_state="processing", + transfer_backend=UploadTransferBackend.local_path, + resumable=False, + index_after_import=True, + index_modalities=("scene", "dialogue"), + expires_at=now.replace(year=now.year + 1), + maximum_files=10, + maximum_file_bytes=50 * 1024 * 1024 * 1024, + maximum_aggregate_bytes=50 * 1024 * 1024 * 1024, + file_count=2, + total_bytes=20, + reserved_file_count=2, + reserved_bytes=20, + uploaded_file_count=2, + uploaded_bytes=20, + ready_file_count=0, + searchable_file_count=0, + failed_file_count=0, + index_failed_file_count=0, + status="VidXP is importing the selected Premiere media.", + next_action="Poll this ingestion for progress.", + ) + + def evidence_job() -> Job: artifact = Artifact( artifact_id=ARTIFACT_ID, @@ -171,6 +206,7 @@ def context( mcp_limit: int = 4 * 1024 * 1024, allowed_origins: tuple[str, ...] = (), remote_uploads: bool = False, + bind_host: str = "127.0.0.1", ) -> HttpApplicationContext: settings = VidXPSettings( repository_root=root, @@ -206,6 +242,7 @@ def context( http_max_json_body_bytes=json_limit, mcp_max_request_body_bytes=mcp_limit, http_allowed_origins=allowed_origins, + http_bind_host=bind_host, upload_public_endpoint=( "http://localhost:8080/uploads/" if remote_uploads @@ -423,6 +460,110 @@ def test_workspace_passes_filters_to_application(self): self.assertEqual(command.filename, "batch") self.assertEqual(command.state, MediaState.pending) + def test_local_ingestion_api_delegates_to_durable_batch_workflow(self): + with TemporaryDirectory() as directory: + context = self.context(Path(directory), remote_uploads=True) + status = ingestion_status() + context.application.select_index_modalities.return_value = ( + "scene", + "dialogue", + ) + assert context.uploads is not None + context.uploads.create_local_ingestion.return_value = status + context.uploads.get_status.return_value = status + with TestClient(create_app(context=context)) as client: + created = client.post( + "/api/v1/media/local-ingestions", + headers={"Idempotency-Key": IDEMPOTENCY_KEY}, + json={ + "paths": [ + "C:/Premiere/interview.mp4", + "C:/Premiere/b-roll.mov", + ], + "index_after_import": True, + "modalities": ["scene", "dialogue"], + }, + ) + fetched = client.get( + f"/api/v1/media/local-ingestions/{INGESTION_ID}" + ) + + self.assertEqual(created.status_code, 202) + self.assertEqual( + created.headers["location"], + f"/api/v1/media/local-ingestions/{INGESTION_ID}", + ) + self.assertEqual(created.json()["transfer_backend"], "local_path") + self.assertEqual(fetched.status_code, 200) + context.application.select_index_modalities.assert_called_once_with( + ("scene", "dialogue") + ) + call = context.uploads.create_local_ingestion.call_args + self.assertEqual( + call.args[0], + ( + "C:/Premiere/interview.mp4", + "C:/Premiere/b-roll.mov", + ), + ) + self.assertTrue(call.kwargs["index_after_import"]) + self.assertEqual( + call.kwargs["index_modalities"], + ("scene", "dialogue"), + ) + context.uploads.get_status.assert_called_once_with( + INGESTION_ID, + principal=context.authenticator.authenticate(None), + ) + + def test_local_ingestion_api_reports_an_unconfigured_runtime(self): + with TemporaryDirectory() as directory: + context = self.context(Path(directory)) + with TestClient(create_app(context=context)) as client: + response = client.post( + "/api/v1/media/local-ingestions", + headers={"Idempotency-Key": IDEMPOTENCY_KEY}, + json={ + "paths": ["C:/Premiere/interview.mp4"], + "modalities": ["scene"], + }, + ) + + self.assertEqual(response.status_code, 503) + self.assertEqual( + response.json()["error"]["code"], + "local_ingestion_unavailable", + ) + + def test_local_ingestion_api_is_not_exposed_by_a_shared_server(self): + with TemporaryDirectory() as directory: + context = self.context( + Path(directory), + auth=HttpAuthMode.static, + remote_uploads=True, + bind_host="0.0.0.0", + ) + with TestClient(create_app(context=context)) as client: + response = client.post( + "/api/v1/media/local-ingestions", + headers={ + **self.auth(), + "Idempotency-Key": IDEMPOTENCY_KEY, + }, + json={ + "paths": ["C:/Premiere/interview.mp4"], + "modalities": ["scene"], + }, + ) + + self.assertEqual(response.status_code, 503) + self.assertEqual( + response.json()["error"]["code"], + "local_ingestion_unavailable", + ) + assert context.uploads is not None + context.uploads.create_local_ingestion.assert_not_called() + def test_repository_scopes_are_enforced_per_operation(self): with TemporaryDirectory() as directory: context = self.context( diff --git a/tests/test_control_plane.py b/tests/test_control_plane.py index 446f57b..93a4634 100644 --- a/tests/test_control_plane.py +++ b/tests/test_control_plane.py @@ -52,6 +52,30 @@ def media_asset(media_id: str, filename: str) -> MediaAsset: class ControlPlaneWorkspaceTests(unittest.TestCase): + def test_select_index_modalities_defaults_and_rejects_non_indexable_names(self): + with TemporaryDirectory() as directory: + root = Path(directory) + application = ControlPlaneApplication( + layout=RepositoryLayout(root=root), + capabilities=CapabilityService(create_capability_registry()), + media=Mock(), + artifacts=Mock(), + index_status=lambda: None, + model_cache=root / "models", + ) + + defaults = application.select_index_modalities(None) + selected = application.select_index_modalities(("scene", "sound")) + with self.assertRaises(ApplicationError) as raised: + application.select_index_modalities(("query",)) + + self.assertEqual( + defaults, + ("dialogue", "sound", "scene", "actor", "videoprism"), + ) + self.assertEqual(selected, ("scene", "sound")) + self.assertEqual(raised.exception.detail.code, "invalid_request") + def test_index_preflight_rejects_unknown_capability_with_next_action(self): with TemporaryDirectory() as directory: root = Path(directory) From 6fbe7b55c7450d3f45ba313835359adc9527579a Mon Sep 17 00:00:00 2001 From: Talha Date: Fri, 28 Aug 2026 18:07:20 +0500 Subject: [PATCH 2/8] feat(premiere): add Desktop-managed CEP and UXP installation --- .github/workflows/desktop.yml | 35 + .gitignore | 2 + INSTALLATION_GUIDE.md | 33 +- README.md | 2 +- desktop/src-tauri/src/lib.rs | 58 ++ desktop/src-tauri/src/premiere_integration.rs | 373 +++++++ desktop/src-tauri/tauri.conf.json | 10 +- .../src/components/PremiereIntegration.tsx | 111 ++ desktop/src/components/TargetSummary.tsx | 8 + desktop/src/desktopPackagingContracts.test.ts | 10 +- desktop/src/tauri.ts | 30 + docs/desktop.md | 14 +- docs/integrations/premiere-pro.md | 144 ++- premiere/README.md | 98 +- premiere/cep/CSXS/manifest.xml | 55 + premiere/cep/index.html | 13 + premiere/cep/index.tsx | 17 + premiere/cep/jsx/host.jsx | 121 +++ premiere/docs/MANUAL_TEST_CHECKLIST.md | 39 +- premiere/index.tsx | 3 +- premiere/package-lock.json | 985 +++++++++++++++++- premiere/package.json | 9 +- premiere/public/styles.css | 56 + premiere/scripts/package-extensions.mjs | 60 ++ premiere/scripts/prepare-cep.mjs | 12 + premiere/src/premiere/cep-adapter.ts | 43 + premiere/src/services/vidxp/cep-fetch.ts | 75 ++ premiere/src/ui/App.tsx | 17 +- premiere/src/ui/components/Spectrum.tsx | 95 +- premiere/tests/cep-adapter.test.ts | 41 + premiere/vite.config.mjs | 65 +- 31 files changed, 2443 insertions(+), 191 deletions(-) create mode 100644 desktop/src-tauri/src/premiere_integration.rs create mode 100644 desktop/src/components/PremiereIntegration.tsx create mode 100644 premiere/cep/CSXS/manifest.xml create mode 100644 premiere/cep/index.html create mode 100644 premiere/cep/index.tsx create mode 100644 premiere/cep/jsx/host.jsx create mode 100644 premiere/scripts/package-extensions.mjs create mode 100644 premiere/scripts/prepare-cep.mjs create mode 100644 premiere/src/premiere/cep-adapter.ts create mode 100644 premiere/src/services/vidxp/cep-fetch.ts create mode 100644 premiere/tests/cep-adapter.test.ts diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index 33f465d..50eb03e 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -62,9 +62,39 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + premiere-packages: + name: Premiere extension packages + runs-on: windows-2025 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.checkout_ref || github.event.inputs.checkout_ref || github.sha }} + + - uses: actions/setup-node@v7 + with: + node-version: "22" + cache: npm + cache-dependency-path: premiere/package-lock.json + + - name: Build and package both Premiere host generations + run: | + npm ci + npm run check + npm run package + working-directory: premiere + + - name: Preserve the Premiere packages + uses: actions/upload-artifact@v7 + with: + name: vidxp-premiere-packages + path: premiere/packages/* + if-no-files-found: error + retention-days: ${{ inputs.artifact_retention_days || 14 }} + build: name: ${{ matrix.target == 'windows' && 'Windows x86-64 NSIS' || matrix.target == 'macos' && 'macOS Apple Silicon DMG' || 'Linux x86-64 AppImage' }} runs-on: ${{ matrix.target == 'windows' && 'windows-2025' || matrix.target == 'macos' && 'macos-15' || 'ubuntu-24.04' }} + needs: premiere-packages strategy: fail-fast: false matrix: @@ -75,6 +105,11 @@ jobs: with: ref: ${{ inputs.checkout_ref || github.event.inputs.checkout_ref || github.sha }} + - uses: actions/download-artifact@v8 + with: + name: vidxp-premiere-packages + path: premiere/packages/ + - uses: actions/download-artifact@v8 if: inputs.package_artifact_name != '' with: diff --git a/.gitignore b/.gitignore index ef41dfc..a82b576 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ pnpm-debug.log* /desktop/dist/ /premiere/dist/ /premiere/coverage/ +/premiere/packages/ +/premiere/.rnd /desktop/src-tauri/target/ /desktop/src-tauri/gen/ /desktop/src-tauri/binaries/uv-* diff --git a/INSTALLATION_GUIDE.md b/INSTALLATION_GUIDE.md index 6394426..a79427a 100644 --- a/INSTALLATION_GUIDE.md +++ b/INSTALLATION_GUIDE.md @@ -79,9 +79,10 @@ setup leaves the previous working setup available. - **App integration service** lets other local applications use the HTTP API or Streamable HTTP MCP. -The [Premiere Pro extension preview](docs/integrations/premiere-pro.md) uses -the app integration service to search media that is already loaded in an -editing project. +The [Premiere Pro extension](docs/integrations/premiere-pro.md) uses the app +integration service to search media that is already loaded in an editing +project. Desktop includes both supported Adobe extension packages; users do +not need the source repository or a JavaScript toolchain. Choose where models should be stored, then decide whether to download them during setup. VidXP displays the required downloads before starting them. @@ -98,6 +99,32 @@ After setup finishes: Browser and API sharing are off by default. If you enable sharing, use it only on a trusted local network and follow the warning shown by Desktop. +### Install the Premiere Pro extension + +On Windows, VidXP supports Premiere Pro 23.0 or newer with two packaged host +variants: + +- Premiere Pro 23.0 through 25.5 uses the CEP extension; +- Premiere Pro 25.6 or newer uses the UXP extension. + +In Desktop, enable **Local video processing** and **App integration service**, +then open **Premiere Pro extension** and select **Install for Premiere**. +Desktop detects standard Premiere installations and installs the matching +package through Adobe Creative Cloud's plugin installer. If Adobe requires +confirmation or elevation, finish the Creative Cloud prompt. Restart Premiere +after installation. + +For Premiere Pro 23.2, open the panel from **Window > Extensions (Legacy) > +VidXP Search**. For Premiere Pro 25.6 or newer, use **Window > UXP Plugins > +VidXP Search**. If both Premiere generations are installed, Desktop installs +both packages; their host ranges do not overlap. + +Plain loopback HTTP is blocked by Premiere UXP on macOS. Desktop therefore does +not claim the 25.6+ macOS panel as usable until the integration has a trusted +local HTTPS transport. The CEP package can use its native local transport on +supported macOS Premiere versions, but still requires host validation before a +release claims support. + ## Command line Command-line installation uses diff --git a/README.md b/README.md index ff00f7e..3c19974 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ ordinary MCP tools. - [Python, HTTP, and MCP installation](INSTALLATION_GUIDE.md) - [Local HTTP API and MCP server](docs/local-api.md) -- [Premiere Pro extension preview](docs/integrations/premiere-pro.md) +- [Premiere Pro extension](docs/integrations/premiere-pro.md) - [ChatGPT and Codex plugin integration](docs/integrations/openai-plugin.md) - [Optional capability packages](INSTALLATION_GUIDE.md#optional-dependency-extras) - [Coolify server setup](docs/deployment/coolify.md) diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index d699e42..7a71cda 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -31,6 +31,7 @@ mod background_process; mod browser_readiness; mod lifecycle; mod media_setup; +mod premiere_integration; mod target_profiles; use activation::{ActivationRecovery, ActivationStage, activation_recovery}; @@ -3728,6 +3729,60 @@ async fn install_codex_plugin( .map_err(|error| format!("Codex plugin setup stopped unexpectedly: {error}"))? } +#[tauri::command] +fn premiere_integration_state( + app: AppHandle, +) -> Result { + let resource_dir = app + .path() + .resource_dir() + .map_err(|error| format!("Could not locate VidXP Desktop resources: {error}"))?; + Ok(premiere_integration::state(&resource_dir)) +} + +#[tauri::command] +async fn install_premiere_extensions( + app: AppHandle, + state: tauri::State<'_, DesktopState>, +) -> Result { + let _active = state.active_operations.register()?; + let (profile, _) = selected_target_context(&app)?; + if !profile.surfaces.iter().any(|surface| surface == "server") { + return Err("Enable the App integration service in Setup options before installing the Premiere extension.".into()); + } + let worker_app = app.clone(); + let (result, packages) = tauri::async_runtime::spawn_blocking(move || { + let resource_dir = worker_app + .path() + .resource_dir() + .map_err(|error| format!("Could not locate VidXP Desktop resources: {error}"))?; + premiere_integration::install(&resource_dir) + }) + .await + .map_err(|error| format!("Premiere setup stopped unexpectedly: {error}"))??; + for package in packages { + app.opener() + .open_path(package.display().to_string(), None::<&str>) + .map_err(|error| { + format!( + "Could not open {} with Adobe Creative Cloud: {error}", + package.display() + ) + })?; + } + Ok(result) +} + +#[tauri::command] +async fn uninstall_premiere_extensions( + state: tauri::State<'_, DesktopState>, +) -> Result<(), String> { + let _active = state.active_operations.register()?; + tauri::async_runtime::spawn_blocking(premiere_integration::uninstall) + .await + .map_err(|error| format!("Premiere removal stopped unexpectedly: {error}"))? +} + fn execute_worker_action(app: &AppHandle, action: &str) -> Result { let (profile, paths) = selected_target_context(app)?; if !profile.surfaces.iter().any(|surface| surface == "worker") { @@ -4759,6 +4814,9 @@ pub fn run() { configure_external_installation, mcp_client_config, install_codex_plugin, + premiere_integration_state, + install_premiere_extensions, + uninstall_premiere_extensions, local_worker_status, start_local_worker, stop_local_worker, diff --git a/desktop/src-tauri/src/premiere_integration.rs b/desktop/src-tauri/src/premiere_integration.rs new file mode 100644 index 0000000..27ed232 --- /dev/null +++ b/desktop/src-tauri/src/premiere_integration.rs @@ -0,0 +1,373 @@ +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, + process::Command, +}; + +#[cfg(windows)] +use std::env; +#[cfg(target_os = "macos")] +use std::fs; + +use serde::{Deserialize, Serialize}; + +const CEP_ID: &str = "org.grayhat.vidxp-premiere.cep.search"; +const UXP_ID: &str = "org.grayhat.vidxp-premiere"; +const CEP_PACKAGE: &str = "vidxp-premiere-cep.zxp"; +const UXP_PACKAGE: &str = "vidxp-premiere-uxp.ccx"; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PremiereHostKind { + Cep, + Uxp, + Unsupported, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct PremiereInstallation { + pub display_name: String, + pub version: String, + pub executable: String, + pub host_kind: PremiereHostKind, + pub compatible: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PremiereIntegrationState { + pub installations: Vec, + pub platform_supported: bool, + pub installer_available: bool, + pub cep_package_available: bool, + pub uxp_package_available: bool, + pub cep_installed: bool, + pub uxp_installed: bool, + pub detail: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PremiereInstallResult { + pub installed_hosts: Vec, + pub opened_packages: Vec, + pub detail: String, +} + +#[derive(Deserialize)] +struct WindowsInstallation { + #[serde(rename = "Name")] + name: String, + #[serde(rename = "Version")] + version: String, + #[serde(rename = "Executable")] + executable: String, +} + +pub fn state(resource_dir: &Path) -> PremiereIntegrationState { + let installations = discover_installations(); + let installer = unified_plugin_installer(); + let installed = installer + .as_ref() + .and_then(|path| installer_list(path).ok()) + .unwrap_or_default(); + let cep_package_available = package_path(resource_dir, PremiereHostKind::Cep).is_file(); + let uxp_package_available = package_path(resource_dir, PremiereHostKind::Uxp).is_file(); + let compatible_count = installations.iter().filter(|item| item.compatible).count(); + let detail = if compatible_count == 0 { + "No compatible Premiere installation was found in its standard application folder. Both packages remain available for custom Adobe installations.".into() + } else { + format!( + "Found {compatible_count} compatible Premiere installation{}.", + if compatible_count == 1 { "" } else { "s" } + ) + }; + PremiereIntegrationState { + installations, + platform_supported: cfg!(any(windows, target_os = "macos")), + installer_available: installer.is_some(), + cep_package_available, + uxp_package_available, + cep_installed: installed.contains(CEP_ID), + uxp_installed: installed.contains(UXP_ID), + detail, + } +} + +pub fn install(resource_dir: &Path) -> Result<(PremiereInstallResult, Vec), String> { + if !cfg!(any(windows, target_os = "macos")) { + return Err( + "Premiere extension installation is available on Windows and macOS only.".into(), + ); + } + let installations = discover_installations(); + let mut kinds = installations + .iter() + .filter(|item| item.compatible) + .map(|item| item.host_kind.clone()) + .collect::>(); + if kinds.is_empty() { + if !installations.is_empty() { + return Err("The detected Premiere installations are not supported by this VidXP extension build.".into()); + } + kinds.insert(PremiereHostKind::Cep); + if cfg!(windows) { + kinds.insert(PremiereHostKind::Uxp); + } + } + let installer = unified_plugin_installer(); + let mut installed_hosts = Vec::new(); + let mut opened = Vec::new(); + for kind in kinds { + let package = package_path(resource_dir, kind.clone()); + if !package.is_file() { + return Err(format!( + "The bundled {} package is missing. Reinstall or update VidXP Desktop.", + package.display() + )); + } + if installer + .as_ref() + .is_some_and(|path| installer_install(path, &package).is_ok()) + { + installed_hosts.push(kind); + } else { + opened.push(package); + } + } + let result = PremiereInstallResult { + installed_hosts, + opened_packages: opened + .iter() + .map(|path| path.display().to_string()) + .collect(), + detail: if opened.is_empty() { + "The VidXP extension was installed. Restart Premiere, then open VidXP Search from the Window menu.".into() + } else { + "Adobe's background installer was unavailable or required interaction. Complete the Creative Cloud installation window, then restart Premiere.".into() + }, + }; + Ok((result, opened)) +} + +pub fn uninstall() -> Result<(), String> { + let installer = unified_plugin_installer() + .ok_or_else(|| "Adobe Creative Cloud's plugin installer was not found.".to_string())?; + let mut failures = Vec::new(); + for id in [CEP_ID, UXP_ID] { + if let Err(error) = installer_remove(&installer, id) { + failures.push(error); + } + } + if failures.len() == 2 { + Err(failures.join(" ")) + } else { + Ok(()) + } +} + +fn package_path(resource_dir: &Path, kind: PremiereHostKind) -> PathBuf { + let name = match kind { + PremiereHostKind::Cep => CEP_PACKAGE, + PremiereHostKind::Uxp => UXP_PACKAGE, + PremiereHostKind::Unsupported => unreachable!("unsupported hosts do not have packages"), + }; + resource_dir.join("premiere").join(name) +} + +fn host_kind(version: &str) -> PremiereHostKind { + let mut parts = version + .split('.') + .filter_map(|part| part.parse::().ok()); + let major = parts.next().unwrap_or_default(); + let minor = parts.next().unwrap_or_default(); + if major > 25 || (major == 25 && minor >= 6) { + PremiereHostKind::Uxp + } else if major >= 23 { + PremiereHostKind::Cep + } else { + PremiereHostKind::Unsupported + } +} + +fn discover_installations() -> Vec { + #[cfg(windows)] + return discover_windows_installations(); + #[cfg(target_os = "macos")] + return discover_macos_installations(); + #[cfg(not(any(windows, target_os = "macos")))] + Vec::new() +} + +#[cfg(windows)] +fn discover_windows_installations() -> Vec { + let script = r#" +$items = @() +$roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}) | Where-Object { $_ } | Select-Object -Unique +foreach ($root in $roots) { + $adobe = Join-Path $root 'Adobe' + if (-not (Test-Path -LiteralPath $adobe)) { continue } + Get-ChildItem -LiteralPath $adobe -Directory -Filter 'Adobe Premiere Pro*' -ErrorAction SilentlyContinue | ForEach-Object { + $exe = Join-Path $_.FullName 'Adobe Premiere Pro.exe' + if (Test-Path -LiteralPath $exe) { + $info = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe) + $items += [pscustomobject]@{ Name = $_.Name; Version = $info.ProductVersion; Executable = $exe } + } + } +} +$items | ConvertTo-Json -Compress +"#; + let output = Command::new("powershell.exe") + .args(["-NoProfile", "-NonInteractive", "-Command", script]) + .output(); + let Ok(output) = output else { + return Vec::new(); + }; + if !output.status.success() { + return Vec::new(); + } + parse_windows_installations(&output.stdout) +} + +#[cfg(windows)] +fn parse_windows_installations(payload: &[u8]) -> Vec { + let value: serde_json::Value = match serde_json::from_slice(payload) { + Ok(value) => value, + Err(_) => return Vec::new(), + }; + let values = match value { + serde_json::Value::Array(values) => values, + serde_json::Value::Object(_) => vec![value], + _ => Vec::new(), + }; + values + .into_iter() + .filter_map(|value| { + let item: WindowsInstallation = serde_json::from_value(value).ok()?; + let kind = host_kind(&item.version); + let compatible = kind != PremiereHostKind::Unsupported + && !(cfg!(target_os = "macos") && kind == PremiereHostKind::Uxp); + Some(PremiereInstallation { + display_name: item.name, + version: item.version, + executable: item.executable, + compatible, + host_kind: kind, + }) + }) + .collect() +} + +#[cfg(target_os = "macos")] +fn discover_macos_installations() -> Vec { + let Ok(entries) = fs::read_dir("/Applications") else { + return Vec::new(); + }; + entries + .flatten() + .filter_map(|entry| { + let path = entry.path(); + let name = path.file_name()?.to_str()?; + if !name.starts_with("Adobe Premiere Pro") || path.extension()?.to_str()? != "app" { + return None; + } + let plist = fs::read_to_string(path.join("Contents/Info.plist")).ok()?; + let version = plist_value(&plist, "CFBundleShortVersionString")?; + let kind = host_kind(&version); + let compatible = kind != PremiereHostKind::Unsupported + && !(cfg!(target_os = "macos") && kind == PremiereHostKind::Uxp); + Some(PremiereInstallation { + display_name: name.trim_end_matches(".app").into(), + version, + executable: path.display().to_string(), + compatible, + host_kind: kind, + }) + }) + .collect() +} + +#[cfg(target_os = "macos")] +fn plist_value(plist: &str, key: &str) -> Option { + let marker = format!("{key}"); + let remainder = plist.split_once(&marker)?.1; + let value = remainder + .split_once("")? + .1 + .split_once("")? + .0; + Some(value.trim().into()) +} + +fn unified_plugin_installer() -> Option { + #[cfg(windows)] + { + let program_files = env::var_os("ProgramFiles")?; + let path = PathBuf::from(program_files).join("Common Files/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.exe"); + return path.is_file().then_some(path); + } + #[cfg(target_os = "macos")] + { + let path = PathBuf::from( + "/Library/Application Support/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.app/Contents/MacOS/UnifiedPluginInstallerAgent", + ); + return path.is_file().then_some(path); + } + #[cfg(not(any(windows, target_os = "macos")))] + None +} + +fn installer_list(installer: &Path) -> Result { + let argument = if cfg!(windows) { "/list" } else { "--list" }; + checked_installer(installer, [argument, "all"]) +} + +fn installer_install(installer: &Path, package: &Path) -> Result { + let argument = if cfg!(windows) { + "/install" + } else { + "--install" + }; + checked_installer(installer, [argument, package.to_string_lossy().as_ref()]) +} + +fn installer_remove(installer: &Path, id: &str) -> Result { + let argument = if cfg!(windows) { "/remove" } else { "--remove" }; + checked_installer(installer, [argument, id]) +} + +fn checked_installer<'a>( + installer: &Path, + arguments: impl IntoIterator, +) -> Result { + let output = Command::new(installer) + .args(arguments) + .output() + .map_err(|error| format!("Could not start Adobe's plugin installer: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + if output.status.success() { + Ok(stdout) + } else { + Err(format!( + "Adobe's plugin installer failed{}.", + if stderr.is_empty() { + String::new() + } else { + format!(": {stderr}") + } + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chooses_non_overlapping_host_generations() { + assert_eq!(host_kind("23.2.0.69"), PremiereHostKind::Cep); + assert_eq!(host_kind("25.5.0"), PremiereHostKind::Cep); + assert_eq!(host_kind("25.6.0"), PremiereHostKind::Uxp); + assert_eq!(host_kind("26.3.0"), PremiereHostKind::Uxp); + assert_eq!(host_kind("22.6.0"), PremiereHostKind::Unsupported); + } +} diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index fe905f2..94f7fd7 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -36,10 +36,12 @@ "targets": "all", "publisher": "Grayhat Developers PVT Ltd", "copyright": "Copyright © 2026 Grayhat Developers PVT Ltd", - "resources": [ - "../THIRD_PARTY_NOTICES.txt", - "../../LICENSE" - ], + "resources": { + "../THIRD_PARTY_NOTICES.txt": "THIRD_PARTY_NOTICES.txt", + "../../LICENSE": "LICENSE", + "../../premiere/packages/vidxp-premiere-cep.zxp": "premiere/vidxp-premiere-cep.zxp", + "../../premiere/packages/vidxp-premiere-uxp.ccx": "premiere/vidxp-premiere-uxp.ccx" + }, "externalBin": [ "binaries/uv" ], diff --git a/desktop/src/components/PremiereIntegration.tsx b/desktop/src/components/PremiereIntegration.tsx new file mode 100644 index 0000000..b1367b3 --- /dev/null +++ b/desktop/src/components/PremiereIntegration.tsx @@ -0,0 +1,111 @@ +import { Alert, Badge, Button, Code, Group, Loader, Stack, Text, Title } from '@mantine/core'; +import { IconDownload, IconRefresh, IconTrash } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; + +import { + errorMessage, + installPremiereExtensions, + localServerStatus, + premiereIntegrationState, + startLocalServer, + uninstallPremiereExtensions, + type PremiereInstallResult, + type PremiereIntegrationState as IntegrationState, +} from '../tauri'; + +interface PremiereIntegrationProps { + operationPending?: boolean; + serverEnabled: boolean; + onOpenSetup: () => void; +} + +export function PremiereIntegration({ operationPending, serverEnabled, onOpenSetup }: PremiereIntegrationProps) { + const [state, setState] = useState(null); + const [busy, setBusy] = useState<'refresh' | 'install' | 'remove' | null>('refresh'); + const [failure, setFailure] = useState(null); + const [result, setResult] = useState(null); + + async function refresh() { + setBusy('refresh'); + setFailure(null); + try { + setState(await premiereIntegrationState()); + } catch (error) { + setFailure(errorMessage(error, 'VidXP could not inspect Premiere installations.')); + } finally { + setBusy(null); + } + } + + useEffect(() => { void refresh(); }, []); + + async function install() { + setBusy('install'); + setFailure(null); + setResult(null); + try { + const server = await localServerStatus(); + if (!server.running) await startLocalServer(); + setResult(await installPremiereExtensions()); + setState(await premiereIntegrationState()); + } catch (error) { + setFailure(errorMessage(error, 'VidXP could not install the Premiere extension.')); + } finally { + setBusy(null); + } + } + + async function remove() { + setBusy('remove'); + setFailure(null); + setResult(null); + try { + await uninstallPremiereExtensions(); + setState(await premiereIntegrationState()); + } catch (error) { + setFailure(errorMessage(error, 'VidXP could not remove the Premiere extension.')); + } finally { + setBusy(null); + } + } + + const installed = Boolean(state?.cep_installed || state?.uxp_installed); + const unavailablePackage = state && (!state.cep_package_available || !state.uxp_package_available); + + return ( +
+ +
+ Premiere Pro extension + Desktop installs the matching extension for every compatible Premiere version it finds. No repository, Node.js, or developer mode is required. +
+ +
+ + {busy === 'refresh' && !state ? : state && + {!state.platform_supported + ? Desktop can install the Premiere extension on Windows and macOS. + : state.installations.length === 0 + ? {state.detail} Installing is still safe: the CEP and UXP packages have non-overlapping host ranges. + : state.installations.map((installation) => +
{installation.display_name}Version {installation.version} · {installation.host_kind === 'cep' ? 'CEP extension' : installation.host_kind === 'uxp' ? 'UXP extension' : 'Not supported'}{installation.executable}
+ {installation.compatible ? 'Compatible' : 'Unsupported'} +
)} + + + Premiere 23–25.5 {state.cep_installed ? 'installed' : 'available'} + Premiere 25.6+ {state.uxp_installed ? 'installed' : 'available'} + + {!serverEnabled && The panel needs VidXP's local service to search and index media.} + {!state.installer_available && state.platform_supported && Adobe's background plugin installer was not found. VidXP will open the packaged extension so Creative Cloud can finish the installation.} + {unavailablePackage && Reinstall or update VidXP Desktop. Release installers include both Premiere packages.} + {result && {result.detail}} + {failure && {failure}} + + {installed && } + + +
} +
+ ); +} diff --git a/desktop/src/components/TargetSummary.tsx b/desktop/src/components/TargetSummary.tsx index 3311ae8..a8cfeda 100644 --- a/desktop/src/components/TargetSummary.tsx +++ b/desktop/src/components/TargetSummary.tsx @@ -2,6 +2,8 @@ import { Alert, Badge, Button, Checkbox, Code, Group, Loader, Modal, Stack, Text import { IconActivityHeartbeat, IconCopy, IconExternalLink, IconPlugConnected, IconPlayerPlay, IconPlayerStop, IconRefresh, IconSettings, IconShare, IconTerminal2 } from '@tabler/icons-react'; import { useEffect, useRef, useState } from 'react'; +import { PremiereIntegration } from './PremiereIntegration'; + import { errorMessage, browserServiceStatus, @@ -396,6 +398,12 @@ export function TargetSummary({ profile, validationError, checking, operationPen + void openExternalSetup()} + /> +
Health and background services Check whether VidXP is usable and control only the services you enabled. diff --git a/desktop/src/desktopPackagingContracts.test.ts b/desktop/src/desktopPackagingContracts.test.ts index cdbb9b7..70dde38 100644 --- a/desktop/src/desktopPackagingContracts.test.ts +++ b/desktop/src/desktopPackagingContracts.test.ts @@ -13,10 +13,12 @@ function read(path: string) { describe('Desktop packaging and documentation contracts', () => { it('packages the project license and complete third-party notices', () => { const config = JSON.parse(read('desktop/src-tauri/tauri.conf.json')); - expect(config.bundle.resources).toEqual(expect.arrayContaining([ - '../THIRD_PARTY_NOTICES.txt', - '../../LICENSE', - ])); + expect(config.bundle.resources).toMatchObject({ + '../THIRD_PARTY_NOTICES.txt': 'THIRD_PARTY_NOTICES.txt', + '../../LICENSE': 'LICENSE', + '../../premiere/packages/vidxp-premiere-cep.zxp': 'premiere/vidxp-premiere-cep.zxp', + '../../premiere/packages/vidxp-premiere-uxp.ccx': 'premiere/vidxp-premiere-uxp.ccx', + }); const notices = read('desktop/THIRD_PARTY_NOTICES.txt'); expect(notices).toContain('VIDXP PROJECT LICENSE'); diff --git a/desktop/src/tauri.ts b/desktop/src/tauri.ts index 78df41e..3b76be2 100644 --- a/desktop/src/tauri.ts +++ b/desktop/src/tauri.ts @@ -264,6 +264,33 @@ export interface CodexPluginInstallResult { detail: string; } +export type PremiereHostKind = 'cep' | 'uxp' | 'unsupported'; + +export interface PremiereInstallation { + display_name: string; + version: string; + executable: string; + host_kind: PremiereHostKind; + compatible: boolean; +} + +export interface PremiereIntegrationState { + installations: PremiereInstallation[]; + platform_supported: boolean; + installer_available: boolean; + cep_package_available: boolean; + uxp_package_available: boolean; + cep_installed: boolean; + uxp_installed: boolean; + detail: string; +} + +export interface PremiereInstallResult { + installed_hosts: PremiereHostKind[]; + opened_packages: string[]; + detail: string; +} + interface WireInstallTransitionResult { install: InstallRuntimeResult; setup: WireTargetState; @@ -385,6 +412,9 @@ export function configureExternalInstallation(capabilities: string[], surfaces: } export function mcpClientConfig(): Promise { return invoke('mcp_client_config'); } export function installCodexPlugin(): Promise { return invoke('install_codex_plugin'); } +export function premiereIntegrationState(): Promise { return invoke('premiere_integration_state'); } +export function installPremiereExtensions(): Promise { return invoke('install_premiere_extensions'); } +export function uninstallPremiereExtensions(): Promise { return invoke('uninstall_premiere_extensions'); } export function localWorkerStatus(): Promise { return invoke('local_worker_status'); } export function startLocalWorker(): Promise { return invoke('start_local_worker'); } export function stopLocalWorker(): Promise { return invoke('stop_local_worker'); } diff --git a/docs/desktop.md b/docs/desktop.md index 15b7c29..b06ce12 100644 --- a/docs/desktop.md +++ b/docs/desktop.md @@ -215,8 +215,18 @@ VidXP currently publishes: | macOS Apple Silicon | DMG | | Linux x86-64 | AppImage | -The release workflow builds all three packages from the stamped release commit -and attaches them to one GitHub release. +The release workflow builds all three Desktop packages from the stamped +release commit and attaches them to one GitHub release. Before those builds it +also creates the signed CEP `.zxp` and UXP `.ccx` Premiere packages. Every +Desktop installer embeds both packages as resources, so installation happens +on the user's computer without source code or build tooling. + +Desktop detects standard Premiere installations, assigns versions 23.0–25.5 +to CEP and 25.6 or newer to UXP, and calls Adobe's Unified Plugin Installer +Agent. If Adobe requires an interactive confirmation, Desktop opens the +already-built package with Creative Cloud. The package host ranges are +non-overlapping, so both may remain installed on a workstation with multiple +Premiere generations. Beta and stable macOS DMGs are signed with a Developer ID certificate and the hardened runtime. The workflow then notarizes, staples, and verifies each DMG diff --git a/docs/integrations/premiere-pro.md b/docs/integrations/premiere-pro.md index 118ce6b..c9591f8 100644 --- a/docs/integrations/premiere-pro.md +++ b/docs/integrations/premiere-pro.md @@ -1,76 +1,69 @@ # Search Premiere Pro media with VidXP -The VidXP Premiere Pro extension preview lets an editor choose clips or bins -from the open Premiere project, index the underlying video files, and search -the resulting library without leaving Premiere. Search features are discovered -from the connected VidXP runtime, so the panel does not assume a fixed list of -capabilities. +The VidXP Premiere Pro extension lets an editor select clips or bins from the +open project, index their existing source files, and search the resulting +library without leaving Premiere. It discovers dialogue, sound, scene, actor, +and future search features from the connected VidXP runtime instead of keeping +a fixed capability list in the extension. -This is a source-built proof of concept. It targets Premiere Pro 25.6 or newer -on the current UXP platform. It has automated client and build coverage, but -the repository does not yet claim host validation or a packaged `.ccx` -release. +## Install the extension -## Prepare VidXP Desktop +Install VidXP Desktop from the official +[GitHub release](https://github.com/grayhatdevelopers/vidxp/releases). No Git +checkout, Node.js installation, local build, Adobe developer mode, or Premiere +upgrade is required for Premiere Pro 23.2. -The extension and VidXP must run on the same computer because the panel passes -Premiere's existing local media paths to VidXP. It does not upload or duplicate -the source videos. +1. In Desktop setup, enable **Local video processing** and **App integration + service** and select the search features you want. +2. On the Desktop summary screen, find **Premiere Pro extension** and select + **Install for Premiere**. +3. Complete an Adobe Creative Cloud confirmation window if one appears. +4. Restart Premiere Pro. -1. In VidXP Desktop, enable **Local video processing** and - **App integration service** for the selected installation. -2. Prepare the search features you intend to use. -3. Start **Local video processing**. -4. Under **App integration service**, choose **Start locally** and copy the - displayed **API address**. +Desktop ships both Adobe extension packages and chooses from the installed +Premiere versions: -Keep the service private to the computer. The Premiere preview does not need -the shared-network mode or a bearer token for this setup. +| Premiere version | Extension | Open the panel from | +|---|---|---| +| 23.0–25.5 | CEP 11 | **Window > Extensions (Legacy) > VidXP Search** | +| 25.6 or newer | UXP | **Window > UXP Plugins > VidXP Search** | -The command-line equivalent is: +The package ranges do not overlap. A workstation with an older and a current +Premiere installation can keep both packages installed without duplicate +panels in either host. -```bash -uv tool install --python 3.14 --torch-backend cpu \ - "vidxp[local-worker,server]" -vidxp init -vidxp prepare -vidxp-api -``` +Desktop uses Adobe Creative Cloud's official Unified Plugin Installer Agent. +If the background installer is unavailable or needs user interaction, Desktop +opens the bundled `.zxp` or `.ccx` package so Creative Cloud can finish the +installation. It never builds extension code on the user's computer. -The default API address is `http://127.0.0.1:32191`. Desktop may choose a -different available local port; always use the address it displays. +## Connect VidXP -## Build and load the preview +The extension and VidXP must run on the same computer because Premiere gives +the panel paths to media already present in the project. VidXP indexes those +source files in place; it does not upload or duplicate them. -Install Node.js 22.19, 24.15, or 26 and Adobe UXP Developer Tool 2.2 or newer. -From the repository root, run: +1. In Desktop, start **Local video processing**. +2. Under **App integration service**, choose **Start locally**. +3. Copy the displayed API address into the Premiere panel and connect. -```bash -npm --prefix premiere ci -npm --prefix premiere run build -``` - -In UXP Developer Tool, add `premiere/dist/manifest.json`, load the plugin, then -open **Window > UXP Plugins > VidXP Search** in Premiere Pro. - -Adobe's supported distribution format is a `.ccx` package installed through -Creative Cloud Desktop. Packaging and signing are intentionally deferred until -the preview passes the host checklist across supported Premiere versions. +Desktop can choose an available local port, so use its displayed address +instead of assuming the default `http://127.0.0.1:32191`. ## Index Premiere media -1. Open a Premiere project and connect the panel to the VidXP API address. +1. Open a Premiere project and connect the panel. 2. Search the project tree or choose **Use current selection** to mirror the Project panel selection. -3. Select clips or bins. A bin expands to its file-backed descendants; Premiere - bins are logical containers and do not imply a filesystem directory. -4. Choose the indexing features reported by VidXP and start indexing. -5. Keep the panel open while it reports per-batch progress. A dismissible - notification reports completion or individual failures. +3. Select clips or bins. A Premiere bin expands to its file-backed descendants; + it is not treated as a filesystem directory. +4. Choose any indexing features reported by VidXP and start indexing. +5. Keep the panel open while it reports batch progress. A dismissible notice + reports completion and identifies individual failures. Offline clips, sequences, generated items without a media path, and duplicate -source paths are not submitted. Selections larger than ten files are divided -into durable VidXP ingestion sessions of ten files each. +source paths are not submitted. Large selections are split into durable VidXP +ingestion sessions. ## Search indexed moments @@ -78,25 +71,22 @@ Enter a description, choose one indexed video or the complete active library, and select any searchable features reported by VidXP. Results show the source video, time range, contributing features, and fused score. -The proof of concept does not yet move Premiere's playhead, open a result in the -Source Monitor, create timeline markers, or insert generated snippets. Those -operations remain behind the Premiere adapter so they can be added without -changing the VidXP client or search UI contracts. - -## Current compatibility limits - -- Adobe documents Premiere UXP as available from Premiere Pro 25.6 with - manifest v5 and UXP Developer Tool 2.2. -- Adobe documents `getMediaFilePath()` for file-backed project items, but proxy, - subclip, Productions, UNC, and offline-media behavior still needs host - coverage. -- Adobe restricts ordinary `http://` network access on macOS. The loopback - transport in this preview must be validated before macOS support is claimed. -- React 19, built-in Spectrum control events and values, panel resize behavior, - network permissions with Desktop's dynamic port, and UXP request-origin - behavior remain manual release gates. -- Completion appears inside the panel. UXP does not expose a dependable native - Premiere toast API, and the preview does not rely on modal `alert()` calls. +Timeline navigation, Source Monitor actions, marker creation, and snippet +insertion remain future host-adapter operations. They can be added without +changing the VidXP client or shared search workflow. + +## Current release limits + +- Windows is the first supported packaging target. Premiere Pro 23.2 must pass + the CEP host checklist before the release is promoted beyond preview. +- Adobe blocks ordinary `http://` URLs in Premiere UXP on macOS. The 25.6+ + macOS panel needs a trusted loopback HTTPS transport before it is supported. +- CEP on macOS uses its native Node transport, but installation and media-path + behavior still require host validation. +- Proxy, subclip, Productions, UNC, mounted-volume, offline-media, and React 19 + control behavior remain explicit manual release gates. +- Completion appears inside the panel because neither host generation exposes + a dependable native Premiere notification API for this workflow. Contributors should read the [extension architecture](../../premiere/README.md) and run the [manual host checklist](../../premiere/docs/MANUAL_TEST_CHECKLIST.md). @@ -104,8 +94,8 @@ and run the [manual host checklist](../../premiere/docs/MANUAL_TEST_CHECKLIST.md ## Adobe references - [Premiere UXP introduction](https://developer.adobe.com/premiere-pro/uxp/introduction/) -- [Premiere UXP manifest](https://developer.adobe.com/premiere-pro/uxp/plugins/concepts/manifest/) -- [Premiere network operations](https://developer.adobe.com/premiere-pro/uxp/resources/recipes/network/) -- [ClipProjectItem API](https://developer.adobe.com/premiere-pro/uxp/ppro-reference/classes/clipprojectitem) -- [Official Premiere UXP samples](https://github.com/AdobeDocs/uxp-premiere-pro-samples) -- [Package and install a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/package/) +- [Package a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/package/) +- [Install a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/install/) +- [Premiere UXP network operations](https://developer.adobe.com/premiere-pro/uxp/resources/recipes/network/) +- [Adobe CEP PProPanel sample](https://github.com/Adobe-CEP/Samples/tree/master/PProPanel) +- [Adobe CEP 11 cookbook](https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_11.x/Documentation/CEP%2011.1%20HTML%20Extension%20Cookbook.md) diff --git a/premiere/README.md b/premiere/README.md index 32d7609..163a708 100644 --- a/premiere/README.md +++ b/premiere/README.md @@ -1,71 +1,77 @@ # VidXP Premiere Pro extension -This directory contains the React and TypeScript UXP panel for using VidXP -inside Premiere Pro. It is independent of the Uplift product; the local Uplift -repository was used only as a packaging and host-boundary reference. +This directory builds the VidXP panel for both Premiere extension generations. +The local Uplift repository was used only as a UXP packaging and host-boundary +reference; this is not an Uplift integration. ## Architecture ```text -React workflow (`src/ui`) -├── typed VidXP HTTP client (`src/services/vidxp`) -└── Premiere UXP adapter (`src/premiere`) - ├── project and sequence context - ├── recursive bin and clip discovery - └── Project panel selection and source-path resolution +Shared React workflow (`src/ui`) +├── typed VidXP client (`src/services/vidxp`) +├── shared media-library rules (`src/premiere/library.ts`) +├── UXP bootstrap + Premiere adapter (`index.tsx`, `adapter.ts`) +└── CEP bootstrap + Premiere adapter (`cep/`, `cep-adapter.ts`) + ├── ES3 ExtendScript project bridge (`cep/jsx/host.jsx`) + └── CEP Node loopback transport (`cep-fetch.ts`) ``` -Only the Premiere adapter imports the host-provided `premierepro` module. The -VidXP client uses HTTP contracts and has no host knowledge. The UI coordinates -selection, ingestion-session polling, and search-job polling without owning -either implementation. +The UI receives a `PremiereAdapter`; it does not import a host API. The UXP +adapter alone imports Premiere's `premierepro` module. The CEP adapter alone +uses `evalScript`, and its ExtendScript bridge returns bounded JSON. Indexing, +polling, search, status, capability discovery, and selection rules are shared. -Standard buttons, text fields, text areas, and checkboxes use the Spectrum UXP -widgets built into Premiere. A small typed React adapter owns native event -listeners, boolean attributes, and controlled values. The panel does not import -Spectrum Web Components or enable the SWC manifest flag. Application-specific -layout, media-tree rows, notices, and result cards remain local CSS. Those -custom surfaces follow Premiere's current theme through `document.theme`; -Premiere does not expose the Photoshop-style `--uxp-host-*` variables. +UXP renders Adobe's built-in Spectrum widgets through the typed control +wrapper. CEP renders native HTML controls through that same wrapper because +Spectrum UXP widgets do not exist in CEP. React remains at version 19 for both +builds, with CEP compiled for Chromium 88. -Capability names, descriptions, indexing support, and searchable roles come -from `GET /api/v1/capabilities`. The panel intentionally contains no mapping -for dialogue, sound, scene, action, or future capabilities. +Capability names and roles come from `GET /api/v1/capabilities`; neither host +adapter hardcodes dialogue, sound, scene, action, or future capability names. -## Development +## Build and package ```bash npm ci npm run check +npm run package ``` -The build writes the loadable UXP bundle to `dist/`. Add -`dist/manifest.json` to UXP Developer Tool; do not add the source manifest. -Vite keeps `premierepro`, `uxp`, and `os` external because Premiere provides -them at runtime. +`npm run check` creates development bundles under `dist/uxp` and `dist/cep`. +`npm run package` additionally creates these ignored release inputs: -Automated tests cover the VidXP client and pure media-library selection rules. -They do not exercise Premiere, UXP, the filesystem, a running VidXP service, or -model inference. Run the [manual checklist](docs/MANUAL_TEST_CHECKLIST.md) for -those boundaries. +- `packages/vidxp-premiere-cep.zxp`, signed and timestamped for Premiere + 23.0–25.5; +- `packages/vidxp-premiere-uxp.ccx` for Premiere 25.6 or newer. + +The Desktop release workflow performs this packaging once and embeds both +artifacts in every Desktop installer. End users do not run these commands. + +For UXP-only development, add `dist/uxp/manifest.json` to UXP Developer Tool. +CEP development uses an unsigned `dist/cep` build and therefore requires the +CEP debugging setup documented by Adobe. Neither developer path is an end-user +installation method. + +Automated tests cover the shared VidXP client, library rules, React build, both +package builds, Desktop resource contract, and Desktop version routing. They do +not exercise Premiere, Creative Cloud, real media paths, a running VidXP +service, or model inference. Run the +[manual checklist](docs/MANUAL_TEST_CHECKLIST.md) for those boundaries. ## POC decisions -- The panel passes file paths returned by Premiere to VidXP's local ingestion - endpoint. It does not request broad UXP filesystem access or copy video bytes. -- VidXP Desktop remains the setup and service-launch surface. The extension is - separately packageable and does not embed or start a Python runtime. +- Premiere file paths are passed to VidXP's local ingestion endpoint; source + video bytes are not copied into extension storage. +- CEP uses its Node HTTP client for loopback requests instead of broadening the + API's browser CORS policy. +- Desktop owns runtime setup, service startup, extension detection, + installation, removal, and user-visible installation status. +- Package host ranges do not overlap: CEP is 23.0–25.5 and UXP begins at 25.6. - Bins expand recursively and source paths are deduplicated before submission. -- React remains at version 19. Built-in Spectrum event delivery and controlled - values are release gates in the manual Premiere checklist. -- The bearer-token password input stays native because Adobe documents a macOS - value-reading bug in the built-in Spectrum password field. The media scope - also stays a native select, matching Adobe's current Premiere sample and - avoiding unnecessary dropdown-index synchronization. -- In-panel progress and completion notices are the primary status UI. +- In-panel progress and completion notices are the primary workflow status. - Timeline navigation and snippet insertion remain future adapter methods. -- Plain loopback HTTP is a Windows POC transport. Cross-platform local transport - remains a packaging decision because Adobe restricts HTTP on macOS. +- Windows is the first release-validation target. Premiere UXP blocks plain + loopback HTTP on macOS, so 25.6+ macOS support needs trusted local HTTPS. -The corresponding user setup is in +The user setup is in [`docs/integrations/premiere-pro.md`](../docs/integrations/premiere-pro.md). diff --git a/premiere/cep/CSXS/manifest.xml b/premiere/cep/CSXS/manifest.xml new file mode 100644 index 0000000..0816fe9 --- /dev/null +++ b/premiere/cep/CSXS/manifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + ./index.html + ./jsx/host.jsx + + --enable-nodejs + --mixed-context + + + + true + + + Panel + VidXP Search + + + 380 + 720 + + + 320 + 480 + + + 1800 + 1800 + + + + + + + diff --git a/premiere/cep/index.html b/premiere/cep/index.html new file mode 100644 index 0000000..fee9812 --- /dev/null +++ b/premiere/cep/index.html @@ -0,0 +1,13 @@ + + + + + + VidXP Search + + + + +
+ + diff --git a/premiere/cep/index.tsx b/premiere/cep/index.tsx new file mode 100644 index 0000000..f9d71db --- /dev/null +++ b/premiere/cep/index.tsx @@ -0,0 +1,17 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import { createCepPremiereAdapter } from "../src/premiere/cep-adapter"; +import { createCepFetch } from "../src/services/vidxp/cep-fetch"; +import { App } from "../src/ui/App"; + +Reflect.set(window, "__VIDXP_CEP__", true); + +const rootElement = document.getElementById("root"); +if (!rootElement) throw new Error("The VidXP panel root element was not found."); + +createRoot(rootElement).render( + + + , +); diff --git a/premiere/cep/jsx/host.jsx b/premiere/cep/jsx/host.jsx new file mode 100644 index 0000000..963d539 --- /dev/null +++ b/premiere/cep/jsx/host.jsx @@ -0,0 +1,121 @@ +/* VidXP's Premiere 23.x bridge. Keep this file ES3-compatible for ExtendScript. */ +$._VIDXP = { + stringify: function (value) { + if (value === null) return "null"; + var type = typeof value; + if (type === "string") { + return '"' + value + .replace(/\\/g, "\\\\") + .replace(/\"/g, '\\"') + .replace(/\r/g, "\\r") + .replace(/\n/g, "\\n") + .replace(/\t/g, "\\t") + '"'; + } + if (type === "number" || type === "boolean") return String(value); + var entries = []; + var index; + if (value instanceof Array) { + for (index = 0; index < value.length; index += 1) { + entries.push($._VIDXP.stringify(value[index])); + } + return "[" + entries.join(",") + "]"; + } + for (var key in value) { + if (value.hasOwnProperty(key)) { + entries.push($._VIDXP.stringify(key) + ":" + $._VIDXP.stringify(value[key])); + } + } + return "{" + entries.join(",") + "}"; + }, + + result: function (operation) { + try { + return $._VIDXP.stringify({ ok: true, value: operation() }); + } catch (error) { + return $._VIDXP.stringify({ + ok: false, + error: error && error.message ? error.message : String(error) + }); + } + }, + + readItem: function (item) { + if (item.type === ProjectItemType.BIN || item.type === ProjectItemType.ROOT) { + var children = []; + for (var childIndex = 0; childIndex < item.children.numItems; childIndex += 1) { + var child = $._VIDXP.readItem(item.children[childIndex]); + if (child !== null) children.push(child); + } + return { + kind: "bin", + id: item.nodeId, + name: item.name, + children: children + }; + } + if (item.type !== ProjectItemType.CLIP && item.type !== ProjectItemType.FILE) return null; + if (item.isSequence()) return null; + if (item.isOffline()) { + return { + kind: "clip", + id: item.nodeId, + name: item.name, + availability: "offline", + detail: "Media is offline in Premiere." + }; + } + var mediaPath = item.getMediaPath(); + if (!mediaPath) { + return { + kind: "clip", + id: item.nodeId, + name: item.name, + availability: "unavailable", + detail: "Premiere did not return a file-backed media path." + }; + } + return { + kind: "clip", + id: item.nodeId, + name: item.name, + nativePath: mediaPath, + availability: "ready" + }; + }, + + getLibrary: function () { + return $._VIDXP.result(function () { + if (!app.project) return null; + var root = app.project.rootItem; + var items = []; + for (var index = 0; index < root.children.numItems; index += 1) { + var node = $._VIDXP.readItem(root.children[index]); + if (node !== null) items.push(node); + } + return { + projectName: app.project.name, + sequenceName: app.project.activeSequence ? app.project.activeSequence.name : null, + items: items + }; + }); + }, + + getSelection: function () { + return $._VIDXP.result(function () { + var ids = []; + var seen = {}; + var viewIds = app.getProjectViewIDs(); + for (var viewIndex = 0; viewIndex < viewIds.length; viewIndex += 1) { + var selected = app.getProjectViewSelection(viewIds[viewIndex]); + for (var itemIndex = 0; itemIndex < selected.length; itemIndex += 1) { + var id = selected[itemIndex].nodeId; + if (!seen[id]) { + seen[id] = true; + ids.push(id); + } + } + } + return ids; + }); + } +}; diff --git a/premiere/docs/MANUAL_TEST_CHECKLIST.md b/premiere/docs/MANUAL_TEST_CHECKLIST.md index 3def2a8..c4d7de6 100644 --- a/premiere/docs/MANUAL_TEST_CHECKLIST.md +++ b/premiere/docs/MANUAL_TEST_CHECKLIST.md @@ -1,17 +1,30 @@ # Premiere Pro manual test checklist -Automated Node tests do not exercise Premiere, UXP, media paths, a real VidXP -process, or model inference. Complete this checklist in Premiere Pro 25.6 or -newer with UXP Developer Tool 2.2 or newer before claiming host support. +Automated tests do not exercise Premiere, CEP, UXP, Creative Cloud, media +paths, a real VidXP process, or model inference. Complete this checklist on a +clean Windows test machine with Premiere Pro 23.2 and Premiere Pro 25.6 or +newer before claiming host support. + +## Package installation + +- Install a release VidXP Desktop build; do not clone the repository or enable + Adobe developer modes for this test. +- Confirm Desktop detects Premiere 23.2 as CEP and Premiere 25.6+ as UXP. +- Select **Install for Premiere** and confirm Adobe's installer accepts the + signed `.zxp` and `.ccx` packages. +- Restart both Premiere versions. Confirm 23.2 shows one panel under **Window > + Extensions (Legacy)** and 25.6+ shows one panel under **Window > UXP + Plugins**. +- Refresh Desktop and confirm both installed statuses are reported. +- Remove both extensions from Desktop, restart Premiere, and confirm both + panels are absent. Reinstall before continuing. ## Load and layout -- Run `npm run check` from `premiere/`. -- Add `premiere/dist/manifest.json` to UXP Developer Tool and load the plugin. -- Open **Window > UXP Plugins > VidXP Search**. +- Run every remaining item once in Premiere 23.2 and once in Premiere 25.6+. - Dock, float, resize, close, and reopen the panel in supported Premiere themes. -- Confirm built-in Spectrum buttons, text fields, text areas, and checkboxes - render with the active Premiere theme. +- Confirm UXP's built-in Spectrum controls and CEP's native controls render + correctly and consistently with the active Premiere theme. - Confirm Tab, Space, and Enter work as expected and focus remains visible. - Confirm controlled values do not reset and actions fire only once after UDT reloads and React Strict Mode remounts. @@ -36,6 +49,8 @@ newer with UXP Developer Tool 2.2 or newer before claiming host support. - In Desktop, start local processing and the app integration service locally. - Copy Desktop's dynamic API address into the extension and connect. +- Confirm CEP connects through its native loopback transport without adding a + `null` browser origin to VidXP's CORS allowlist. - Confirm the panel shows the capabilities and indexed media reported by that runtime, including any capability added after this extension was built. - Stop the service and confirm the panel reports a safe, actionable error. @@ -69,9 +84,9 @@ newer with UXP Developer Tool 2.2 or newer before claiming host support. ## Safety and cleanup -- Confirm no source video is copied into `premiere/`, UXP plugin data, or UXP - temporary storage. +- Confirm no source video is copied into `premiere/`, CEP or UXP plugin data, + or host temporary storage. - Confirm no API token, source path, media file, index, build output, or local UXP Developer Tool setting appears in `git status`. -- Stop services started for the test and remove the plugin from UXP Developer - Tool when finished. +- Stop services started for the test and remove both packages from Desktop when + finished. diff --git a/premiere/index.tsx b/premiere/index.tsx index 6c1d902..05fc9dc 100644 --- a/premiere/index.tsx +++ b/premiere/index.tsx @@ -2,6 +2,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { App } from "./src/ui/App"; +import { createPremiereAdapter } from "./src/premiere/adapter"; // UXP supplies this module inside Premiere at runtime. // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -15,7 +16,7 @@ if (!rootElement) { createRoot(rootElement).render( - + , ); diff --git a/premiere/package-lock.json b/premiere/package-lock.json index ea4390d..c139a17 100644 --- a/premiere/package-lock.json +++ b/premiere/package-lock.json @@ -19,6 +19,7 @@ "@types/node": "24.13.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", + "archiver": "7.0.1", "concurrently": "10.0.5", "eslint": "9.39.2", "eslint-plugin-react-hooks": "7.0.1", @@ -26,7 +27,8 @@ "typescript": "6.0.3", "typescript-eslint": "8.65.0", "vite": "8.2.2", - "vitest": "4.1.11" + "vitest": "4.1.11", + "zxp-signer": "1.0.6" }, "engines": { "node": "^22.19.0 || ^24.15.0 || ^26.0.0", @@ -592,6 +594,67 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -652,6 +715,17 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", @@ -1777,6 +1851,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -1843,6 +1930,123 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1860,6 +2064,28 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1870,6 +2096,112 @@ "node": "18 || 20 || >=22" } }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz", + "integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.2.tgz", + "integrity": "sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.11.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", @@ -1930,6 +2262,41 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2019,6 +2386,23 @@ "dev": true, "license": "MIT" }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2058,6 +2442,40 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2115,6 +2533,13 @@ "node": ">=8" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.415", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", @@ -2447,6 +2872,36 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -2464,6 +2919,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2547,14 +3009,31 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, "os": [ "darwin" ], @@ -2639,6 +3118,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2666,6 +3152,27 @@ "hermes-estree": "0.25.1" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2703,6 +3210,13 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2713,6 +3227,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2726,6 +3250,26 @@ "node": ">=0.10.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2733,6 +3277,22 @@ "dev": true, "license": "ISC" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2820,6 +3380,52 @@ "json-buffer": "3.0.1" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3111,6 +3717,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3207,6 +3820,16 @@ "node": ">=18" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obug": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", @@ -3404,6 +4027,23 @@ "node": ">= 0.8.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3435,6 +4075,63 @@ "react": "^19.2.8" } }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3509,6 +4206,27 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -3571,6 +4289,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3595,6 +4326,28 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -3613,6 +4366,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", @@ -3629,6 +4428,30 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3655,6 +4478,39 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/tar-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.1.tgz", + "integrity": "sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3982,6 +4838,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", @@ -4211,6 +5074,86 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4269,6 +5212,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", @@ -4291,6 +5249,17 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zxp-signer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/zxp-signer/-/zxp-signer-1.0.6.tgz", + "integrity": "sha512-4SxJds9qPdSvGhQ59R/+jD2yLYqkc3gLTOUrRS7CN0Sbb+H/VOZR2DWg5I7OBQCXU31n+WTOF5mGGEhmO7aLhA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } } } } diff --git a/premiere/package.json b/premiere/package.json index 0de9688..dc90d52 100644 --- a/premiere/package.json +++ b/premiere/package.json @@ -10,7 +10,10 @@ }, "scripts": { "clean": "rimraf dist coverage", - "build": "vite build", + "build": "npm run build:uxp && npm run build:cep", + "build:uxp": "vite build --mode uxp", + "build:cep": "vite build --mode cep && node scripts/prepare-cep.mjs", + "package": "npm run build && node scripts/package-extensions.mjs", "dev": "concurrently -k -n build,types \"vite build --watch\" \"tsc --watch --noEmit --preserveWatchOutput\"", "lint": "eslint . --max-warnings 0", "typecheck": "tsc --noEmit", @@ -30,6 +33,7 @@ "@types/node": "24.13.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", + "archiver": "7.0.1", "concurrently": "10.0.5", "eslint": "9.39.2", "eslint-plugin-react-hooks": "7.0.1", @@ -37,6 +41,7 @@ "typescript": "6.0.3", "typescript-eslint": "8.65.0", "vite": "8.2.2", - "vitest": "4.1.11" + "vitest": "4.1.11", + "zxp-signer": "1.0.6" } } diff --git a/premiere/public/styles.css b/premiere/public/styles.css index a20dc5b..fd2569f 100644 --- a/premiere/public/styles.css +++ b/premiere/public/styles.css @@ -48,6 +48,62 @@ sp-checkbox { flex: none; } +.native-button, +.native-control input, +.native-control textarea { + width: 100%; + padding: 8px 9px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--field-background); + color: inherit; + font: inherit; +} + +.native-button { + cursor: pointer; + background: #6652c6; + font-weight: 650; +} + +.native-button.quiet { + width: auto; + background: transparent; +} + +.native-button:disabled { + cursor: default; + opacity: .55; +} + +.native-control { + display: flex; + width: 100%; + flex-direction: column; + gap: 5px; +} + +.native-control > span { + font-size: 11px; + font-weight: 650; +} + +.native-control textarea { + resize: vertical; +} + +.native-checkbox { + display: inline-flex; + align-items: center; + gap: 7px; + font-size: 11px; +} + +.native-checkbox input { + flex: none; + margin: 0; +} + .native-secret, .field select { width: 100%; diff --git a/premiere/scripts/package-extensions.mjs b/premiere/scripts/package-extensions.mjs new file mode 100644 index 0000000..c3fa918 --- /dev/null +++ b/premiere/scripts/package-extensions.mjs @@ -0,0 +1,60 @@ +import archiver from "archiver"; +import { createWriteStream, mkdirSync, rmSync } from "node:fs"; +import { createRequire } from "node:module"; +import { resolve } from "node:path"; +import { spawnSync } from "node:child_process"; + +const require = createRequire(import.meta.url); +const root = resolve(import.meta.dirname, ".."); +const packages = resolve(root, "packages"); +const uxpPackage = resolve(packages, "vidxp-premiere-uxp.ccx"); +const cepPackage = resolve(packages, "vidxp-premiere-cep.zxp"); +const certificate = resolve(packages, "vidxp-premiere-build-certificate.p12"); + +mkdirSync(packages, { recursive: true }); +await archiveDirectory(resolve(root, "dist", "uxp"), uxpPackage); + +const signerRoot = resolve(require.resolve("zxp-signer/package.json"), ".."); +const signerPlatform = process.platform === "win32" + ? process.arch === "x64" ? "win64" : "Win32" + : "osx"; +const signer = resolve(signerRoot, "bin", "4.1.3", signerPlatform, `ZXPSignCmd${process.platform === "win32" ? ".exe" : ""}`); +const certificatePassword = process.env.ZXP_CERT_PASSWORD || "vidxp-release-build"; +runSigner([ + "-selfSignedCert", + process.env.ZXP_CERT_COUNTRY || "PK", + process.env.ZXP_CERT_PROVINCE || "Punjab", + process.env.ZXP_CERT_ORG || "Grayhat Developers PVT Ltd", + process.env.ZXP_CERT_NAME || "org.grayhat.vidxp-premiere.cep", + certificatePassword, + certificate, +]); +const result = runSigner([ + "-sign", + resolve(root, "dist", "cep"), + cepPackage, + certificate, + certificatePassword, + "-tsa", + process.env.ZXP_TIMESTAMP || "http://timestamp.digicert.com/", +], false); +rmSync(certificate, { force: true }); +if (result.status !== 0) process.exit(result.status ?? 1); + +function archiveDirectory(input, output) { + return new Promise((resolveArchive, reject) => { + const stream = createWriteStream(output); + const archive = archiver("zip", { zlib: { level: 9 } }); + stream.on("close", resolveArchive); + archive.on("error", reject); + archive.pipe(stream); + archive.directory(input, false); + void archive.finalize(); + }); +} + +function runSigner(arguments_, exitOnFailure = true) { + const result = spawnSync(signer, arguments_, { stdio: "inherit" }); + if (exitOnFailure && result.status !== 0) process.exit(result.status ?? 1); + return result; +} diff --git a/premiere/scripts/prepare-cep.mjs b/premiere/scripts/prepare-cep.mjs new file mode 100644 index 0000000..93a44fc --- /dev/null +++ b/premiere/scripts/prepare-cep.mjs @@ -0,0 +1,12 @@ +import { cpSync, mkdirSync } from "node:fs"; +import { resolve } from "node:path"; + +const root = resolve(import.meta.dirname, ".."); +const output = resolve(root, "dist", "cep"); + +mkdirSync(resolve(output, "CSXS"), { recursive: true }); +mkdirSync(resolve(output, "jsx"), { recursive: true }); +cpSync(resolve(root, "cep", "index.html"), resolve(output, "index.html")); +cpSync(resolve(root, "cep", "CSXS", "manifest.xml"), resolve(output, "CSXS", "manifest.xml")); +cpSync(resolve(root, "cep", "jsx", "host.jsx"), resolve(output, "jsx", "host.jsx")); +cpSync(resolve(root, "public", "styles.css"), resolve(output, "styles.css")); diff --git a/premiere/src/premiere/cep-adapter.ts b/premiere/src/premiere/cep-adapter.ts new file mode 100644 index 0000000..2007319 --- /dev/null +++ b/premiere/src/premiere/cep-adapter.ts @@ -0,0 +1,43 @@ +import type { PremiereAdapter, PremiereLibrary } from "./types"; + +interface CepBridge { + evalScript(script: string, callback: (result: string) => void): void; +} + +interface BridgeEnvelope { + ok: boolean; + value?: T; + error?: string; +} + +export function createCepPremiereAdapter(): PremiereAdapter { + return { + getLibrary: () => evaluate("$._VIDXP.getLibrary()"), + getSelectedProjectItemIds: () => + evaluate("$._VIDXP.getSelection()"), + }; +} + +function evaluate(script: string): Promise { + const bridge = Reflect.get(window, "__adobe_cep__") as CepBridge | undefined; + if (!bridge) return Promise.reject(new Error("Premiere's CEP bridge is unavailable.")); + + return new Promise((resolve, reject) => { + bridge.evalScript(script, (rawResult) => { + if (!rawResult || rawResult === "EvalScript error.") { + reject(new Error("Premiere could not run the VidXP host bridge.")); + return; + } + try { + const envelope = JSON.parse(rawResult) as BridgeEnvelope; + if (!envelope.ok) { + reject(new Error(envelope.error || "Premiere returned an unknown error.")); + return; + } + resolve(envelope.value as T); + } catch { + reject(new Error("Premiere returned an invalid VidXP host response.")); + } + }); + }); +} diff --git a/premiere/src/services/vidxp/cep-fetch.ts b/premiere/src/services/vidxp/cep-fetch.ts new file mode 100644 index 0000000..af9f273 --- /dev/null +++ b/premiere/src/services/vidxp/cep-fetch.ts @@ -0,0 +1,75 @@ +import type { VidXPFetch } from "./client"; + +interface CepNodeRuntime { + require(name: "http" | "https"): NodeHttpModule; +} + +interface NodeHttpModule { + request( + url: string, + options: { headers: Record; method: string }, + callback: (response: NodeResponse) => void, + ): NodeRequest; +} + +interface NodeResponse { + headers: Record; + statusCode?: number; + statusMessage?: string; + on(event: "data", listener: (chunk: string) => void): void; + on(event: "end", listener: () => void): void; + setEncoding(encoding: "utf8"): void; +} + +interface NodeRequest { + destroy(error?: Error): void; + end(body?: string): void; + on(event: "error", listener: (error: Error) => void): void; +} + +export function createCepFetch(): VidXPFetch { + return (input, init = {}) => { + const requestInit = init as RequestInit; + const url = input instanceof Request ? input.url : String(input); + const runtime = Reflect.get(window, "cep_node") as CepNodeRuntime | undefined; + if (!runtime) return Promise.reject(new Error("Premiere's CEP network runtime is unavailable.")); + const protocol = new URL(url).protocol; + const transport = runtime.require(protocol === "https:" ? "https" : "http"); + const headers = Object.fromEntries(new Headers(requestInit.headers).entries()); + + return new Promise((resolve, reject) => { + const request = transport.request( + url, + { headers, method: requestInit.method || "GET" }, + (response) => { + response.setEncoding("utf8"); + let body = ""; + response.on("data", (chunk) => { + body += chunk; + }); + response.on("end", () => { + const responseHeaders = new Headers(); + for (const [name, value] of Object.entries(response.headers)) { + if (Array.isArray(value)) value.forEach((item) => responseHeaders.append(name, item)); + else if (value !== undefined) responseHeaders.set(name, value); + } + resolve(new Response(body, { + headers: responseHeaders, + status: response.statusCode || 500, + statusText: response.statusMessage, + })); + }); + }, + ); + request.on("error", reject); + requestInit.signal?.addEventListener("abort", () => request.destroy(abortError()), { once: true }); + request.end(typeof requestInit.body === "string" ? requestInit.body : undefined); + }); + }; +} + +function abortError(): Error { + const error = new Error("Operation cancelled"); + error.name = "AbortError"; + return error; +} diff --git a/premiere/src/ui/App.tsx b/premiere/src/ui/App.tsx index 9b80cf0..fecf23c 100644 --- a/premiere/src/ui/App.tsx +++ b/premiere/src/ui/App.tsx @@ -1,16 +1,20 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { createPremiereAdapter } from "../premiere/adapter"; import { chunkPaths, collectSelectedClips, countReadyClips, filterLibrary, } from "../premiere/library"; -import type { PremiereLibrary, PremiereMediaNode } from "../premiere/types"; +import type { + PremiereAdapter, + PremiereLibrary, + PremiereMediaNode, +} from "../premiere/types"; import { createIdempotencyKey, VidXPClient, + type VidXPFetch, } from "../services/vidxp/client"; import type { CapabilitySummary, @@ -45,8 +49,12 @@ interface Notice { const DEFAULT_API_ADDRESS = "http://127.0.0.1:32191"; -export function App() { - const premiere = useMemo(() => createPremiereAdapter(), []); +interface AppProps { + fetchImpl?: VidXPFetch; + premiere: PremiereAdapter; +} + +export function App({ fetchImpl, premiere }: AppProps) { const abortController = useRef(undefined); const [apiAddress, setApiAddress] = useState(DEFAULT_API_ADDRESS); const [bearerToken, setBearerToken] = useState(""); @@ -111,6 +119,7 @@ export function App() { const nextClient = new VidXPClient({ baseUrl: apiAddress, bearerToken, + fetchImpl, }); const [, nextCapabilities, nextWorkspace] = await Promise.all([ nextClient.health(), diff --git a/premiere/src/ui/components/Spectrum.tsx b/premiere/src/ui/components/Spectrum.tsx index a40d814..0b16686 100644 --- a/premiere/src/ui/components/Spectrum.tsx +++ b/premiere/src/ui/components/Spectrum.tsx @@ -58,13 +58,40 @@ interface SpectrumCheckboxProps extends CommonControlProps { } export function SpectrumButton(props: SpectrumButtonProps) { + if (isCepRuntime()) return ; return ; } export function SpectrumActionButton(props: SpectrumActionButtonProps) { + if (isCepRuntime()) return ; return ; } +function NativeButton({ + ariaLabel, + children, + className, + disabled = false, + onPress, + quiet = false, + selected = false, +}: SpectrumActionButtonProps) { + return ( + + ); +} + function SpectrumButtonElement({ tag, ariaLabel, @@ -110,16 +137,33 @@ function SpectrumButtonElement({ ); } -export function SpectrumTextField({ +export function SpectrumTextField(props: SpectrumTextFieldProps) { + return isCepRuntime() ? : ; +} + +function NativeTextField({ ariaLabel, className, disabled = false, label, onValueChange, placeholder, - quiet = false, type = "text", value, +}: SpectrumTextFieldProps) { + return ( + + ); +} + +function UxpTextField({ + ariaLabel, className, disabled = false, label, onValueChange, placeholder, + quiet = false, type = "text", value, }: SpectrumTextFieldProps) { const ref = useRef(null); useUxpProperty(ref, "disabled", disabled); @@ -145,7 +189,11 @@ export function SpectrumTextField({ ); } -export function SpectrumTextArea({ +export function SpectrumTextArea(props: SpectrumTextAreaProps) { + return isCepRuntime() ? : ; +} + +function NativeTextArea({ ariaLabel, className, disabled = false, @@ -154,6 +202,21 @@ export function SpectrumTextArea({ placeholder, quiet = false, value, +}: SpectrumTextAreaProps) { + void quiet; + return ( +