From 98833a87ce069c7c81c48482dc24a2118e2991ca Mon Sep 17 00:00:00 2001 From: Marcel Wege Date: Thu, 20 Aug 2026 18:46:19 +0200 Subject: [PATCH] feat(ui): port the dev-platform operator SPA out of core web-ui (epic byte5ai/omadia#470 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twenty-eight source files that lived in `web-ui/app/admin/dev-platform/**` and `web-ui/app/_components/devjobs/**` now build as a standalone Vite/React 19 bundle in `packages/ui`, ship inside the plugin ZIP as `ui/`, and are served by core at `/p//ui/` behind web-ui's `/plugin-ui/` host page. `web-ui` is untouched. Four screens: hub, job detail, repo detail, add-repo wizard. What replaced what - `next-intl` -> `src/lib/i18n.tsx`. 300 keys per locale, plain `{name}` interpolation, no ICU parser. The three ICU plurals were de-sugared to `{ one, other }` at extraction; en and de share the `n === 1` rule. - `next/link`, `next/navigation` -> `src/lib/router.tsx`, a hash router. The static route serves only the bundle root and real files, so a client route in the PATH would 404 on reload; a fragment never reaches the server. It also avoids baking a plugin id into the build, since the id comes from the install. - core's 4,827-line `app/_lib/api.ts` -> `src/lib/apiError.ts`. Exactly one name was imported from it. - `framer-motion` and `lucide-react` -> dropped. Both animate or size with classes the served vocabulary does not contain. Tailwind, constrained to what core actually serves The pages carried 334 arbitrary values (`text-[color:var(--fg-muted)]` and friends); the ZIP allowlist rejects that shape and a class core never saw renders unstyled rather than erroring. All 334 are gone. `Button` and `ConfirmDialog` were rewritten rather than ported for the same reason — core expresses every Button variant as an arbitrary value. `scripts/check-ui-vocabulary.mjs` gates the build with three checks: no emitted stylesheet, core's two ingest regexes verbatim, and a whitelist diff against `vocabulary/classes.txt` — 690 classes extracted from the generated stylesheet itself, not transcribed from the spec table. The diff is the half core has no counterpart for: ingest cannot see `bg-blue-500`, which is not an arbitrary value, merely a class that does not exist. Packaging and nav `ui/` joins `dist` and `migrations` in build-zip's REQUIRED_DIRS, with assertions that `ui/index.html` exists and that no stylesheet is present. The nav entry moves from `/admin/dev-platform` — a page core deletes in this epic — to `/plugin-ui/`, percent-encoded because this plugin's id is scoped. Tests: 50, covering the four screens against fixtures in both locales, the theme attribute crossing the iframe boundary, the router, the i18n runtime, and the vocabulary gate (fixtures for `w-[137px]`, `[&>tr]:`, `bg-blue-500`). Two mutations were run against the suite and both were killed. Three core defects found and NOT fixable here — see docs/iframe-credentials.md 1. The host page's `sandbox` omits `allow-same-origin`, so every authenticated call from the frame is cross-origin with `Origin: null`. All four screens are data-driven; none can load data in a real browser until this is decided. 2. That page's plugin-id regex rejects scoped ids, though `manifestLoader` blesses them and this plugin's id is `@omadia/dev-platform`. 3. `plugin-ui.source.css` lines 341/346/353 use a top-level comma where `@source inline()` expands only braces, so `border`, `divide-*` and `transition*` emit NOTHING. With the base reset at `border: 0 solid`, `class="border border-border"` renders invisible — the exact silent failure the contract exists to prevent. `src/lib/cx.ts` works around it with the four directional utilities and a test pins the broken state so the workaround cannot rot. --- .github/workflows/ci.yml | 46 + .gitignore | 4 + docs/iframe-credentials.md | 168 + package-lock.json | 4569 ++++++++++++++--- package.json | 9 +- packages/plugin/scripts/build-zip.mjs | 60 +- packages/plugin/src/plugin.ts | 18 +- packages/ui/README.md | 101 +- packages/ui/index.html | 47 + packages/ui/messages/de.json | 398 ++ packages/ui/messages/en.json | 398 ++ packages/ui/package.json | 41 + packages/ui/scripts/check-ui-vocabulary.d.mts | 36 + packages/ui/scripts/check-ui-vocabulary.mjs | 342 ++ packages/ui/scripts/extract-vocabulary.d.mts | 7 + packages/ui/scripts/extract-vocabulary.mjs | 86 + packages/ui/src/App.tsx | 67 + packages/ui/src/components/AddRepoWizard.tsx | 232 + .../ui/src/components/BindGithubAppPanel.tsx | 154 + packages/ui/src/components/ConfirmDialog.tsx | 96 + packages/ui/src/components/CredentialStep.tsx | 140 + .../ui/src/components/DeviceFlowPanel.tsx | 155 + packages/ui/src/components/GateInbox.tsx | 305 ++ .../ui/src/components/GithubAppsPanel.tsx | 177 + packages/ui/src/components/JobLogPane.tsx | 91 + packages/ui/src/components/JobTable.tsx | 203 + packages/ui/src/components/NewJobDialog.tsx | 160 + .../ui/src/components/PhaseArtifactPanel.tsx | 99 + packages/ui/src/components/PrettyArtifact.tsx | 61 + .../ui/src/components/ProtectionCheckList.tsx | 33 + .../ui/src/components/RepoBudgetPanel.tsx | 94 + packages/ui/src/components/RepoTable.tsx | 188 + .../ui/src/components/RepoWebhookPanel.tsx | 87 + .../src/components/ScrollToBottomButton.tsx | 59 + packages/ui/src/components/ToolCallCard.tsx | 167 + .../components/devjobs/DevJobPhaseRail.tsx | 245 + .../components/devjobs/DevJobStatusText.tsx | 73 + packages/ui/src/components/ui/Button.tsx | 114 + packages/ui/src/lib/api.ts | 435 ++ packages/ui/src/lib/apiError.ts | 22 + packages/ui/src/lib/appearance.ts | 104 + packages/ui/src/lib/budget.ts | 70 + packages/ui/src/lib/cx.ts | 55 + packages/ui/src/lib/gates.ts | 18 + packages/ui/src/lib/i18n.tsx | 212 + packages/ui/src/lib/lineDiff.ts | 67 + packages/ui/src/lib/prettyArtifact.ts | 20 + packages/ui/src/lib/router.tsx | 186 + packages/ui/src/lib/toolCallLog.ts | 209 + packages/ui/src/lib/useDevJobEvents.ts | 112 + packages/ui/src/lib/useStickToBottom.ts | 78 + packages/ui/src/main.tsx | 37 + packages/ui/src/screens/HubScreen.tsx | 248 + packages/ui/src/screens/JobDetailScreen.tsx | 326 ++ packages/ui/src/screens/RepoDetailScreen.tsx | 126 + packages/ui/src/screens/RepoNewScreen.tsx | 28 + packages/ui/test/appearance.test.ts | 57 + packages/ui/test/fixtures/clean.js | 2 + packages/ui/test/fixtures/data.ts | 72 + packages/ui/test/fixtures/unknown-class.js | 5 + packages/ui/test/fixtures/w-137px.js | 6 + packages/ui/test/i18n.test.tsx | 120 + packages/ui/test/router.test.tsx | 88 + packages/ui/test/screens.test.tsx | 141 + packages/ui/test/setup.ts | 18 + packages/ui/test/vocabulary.test.ts | 204 + packages/ui/tsconfig.json | 31 + packages/ui/vite.config.ts | 82 + packages/ui/vocabulary/README.md | 69 + packages/ui/vocabulary/classes.txt | 690 +++ 70 files changed, 12468 insertions(+), 800 deletions(-) create mode 100644 docs/iframe-credentials.md create mode 100644 packages/ui/index.html create mode 100644 packages/ui/messages/de.json create mode 100644 packages/ui/messages/en.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/scripts/check-ui-vocabulary.d.mts create mode 100644 packages/ui/scripts/check-ui-vocabulary.mjs create mode 100644 packages/ui/scripts/extract-vocabulary.d.mts create mode 100644 packages/ui/scripts/extract-vocabulary.mjs create mode 100644 packages/ui/src/App.tsx create mode 100644 packages/ui/src/components/AddRepoWizard.tsx create mode 100644 packages/ui/src/components/BindGithubAppPanel.tsx create mode 100644 packages/ui/src/components/ConfirmDialog.tsx create mode 100644 packages/ui/src/components/CredentialStep.tsx create mode 100644 packages/ui/src/components/DeviceFlowPanel.tsx create mode 100644 packages/ui/src/components/GateInbox.tsx create mode 100644 packages/ui/src/components/GithubAppsPanel.tsx create mode 100644 packages/ui/src/components/JobLogPane.tsx create mode 100644 packages/ui/src/components/JobTable.tsx create mode 100644 packages/ui/src/components/NewJobDialog.tsx create mode 100644 packages/ui/src/components/PhaseArtifactPanel.tsx create mode 100644 packages/ui/src/components/PrettyArtifact.tsx create mode 100644 packages/ui/src/components/ProtectionCheckList.tsx create mode 100644 packages/ui/src/components/RepoBudgetPanel.tsx create mode 100644 packages/ui/src/components/RepoTable.tsx create mode 100644 packages/ui/src/components/RepoWebhookPanel.tsx create mode 100644 packages/ui/src/components/ScrollToBottomButton.tsx create mode 100644 packages/ui/src/components/ToolCallCard.tsx create mode 100644 packages/ui/src/components/devjobs/DevJobPhaseRail.tsx create mode 100644 packages/ui/src/components/devjobs/DevJobStatusText.tsx create mode 100644 packages/ui/src/components/ui/Button.tsx create mode 100644 packages/ui/src/lib/api.ts create mode 100644 packages/ui/src/lib/apiError.ts create mode 100644 packages/ui/src/lib/appearance.ts create mode 100644 packages/ui/src/lib/budget.ts create mode 100644 packages/ui/src/lib/cx.ts create mode 100644 packages/ui/src/lib/gates.ts create mode 100644 packages/ui/src/lib/i18n.tsx create mode 100644 packages/ui/src/lib/lineDiff.ts create mode 100644 packages/ui/src/lib/prettyArtifact.ts create mode 100644 packages/ui/src/lib/router.tsx create mode 100644 packages/ui/src/lib/toolCallLog.ts create mode 100644 packages/ui/src/lib/useDevJobEvents.ts create mode 100644 packages/ui/src/lib/useStickToBottom.ts create mode 100644 packages/ui/src/main.tsx create mode 100644 packages/ui/src/screens/HubScreen.tsx create mode 100644 packages/ui/src/screens/JobDetailScreen.tsx create mode 100644 packages/ui/src/screens/RepoDetailScreen.tsx create mode 100644 packages/ui/src/screens/RepoNewScreen.tsx create mode 100644 packages/ui/test/appearance.test.ts create mode 100644 packages/ui/test/fixtures/clean.js create mode 100644 packages/ui/test/fixtures/data.ts create mode 100644 packages/ui/test/fixtures/unknown-class.js create mode 100644 packages/ui/test/fixtures/w-137px.js create mode 100644 packages/ui/test/i18n.test.tsx create mode 100644 packages/ui/test/router.test.tsx create mode 100644 packages/ui/test/screens.test.tsx create mode 100644 packages/ui/test/setup.ts create mode 100644 packages/ui/test/vocabulary.test.ts create mode 100644 packages/ui/tsconfig.json create mode 100644 packages/ui/vite.config.ts create mode 100644 packages/ui/vocabulary/README.md create mode 100644 packages/ui/vocabulary/classes.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8e3177..b8546f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,10 +150,56 @@ jobs: exit 1 fi + # The SPA vocabulary gate. + # + # `npm run build -w packages/ui` already runs this check — it is the + # second half of that package’s `build` script. Running it again, + # standalone, catches the one edit the build itself cannot see: a + # `vocabulary/classes.txt` widened by hand to turn a red build green. + # That passes either way inside the build; here it is the only subject. + # + # A class outside the served sheet does not error at runtime. It renders + # UNSTYLED, on the operator’s screen, and nowhere else. + - name: Assert the plugin UI vocabulary + working-directory: omadia-dev-platform/packages/ui + run: npm run lint:vocabulary + - name: Package working-directory: omadia-dev-platform run: npm run package -w packages/plugin + # `ui/` is in build-zip’s REQUIRED_DIRS, so a missing bundle already + # fails the step above. This asserts the property that step cannot: that + # the archive carries NO stylesheet. `.css` is absent from the plugin-ZIP + # extension allowlist, so a ZIP containing one is rejected at ingest — + # after upload, by someone else, with a message that does not name this + # build. `set -o pipefail` because a `grep` behind a pipe that dies on a + # 64 KiB buffer is how this repo’s sibling shipped a dead release + # pipeline for months. + - name: Assert the ZIP ships a bundle and no stylesheet + working-directory: omadia-dev-platform + shell: bash + run: | + set -euo pipefail + # Exactly one, not `ls *.zip` — a stale artifact from an earlier + # version makes that glob expand to two paths and `unzip` then + # reads the second as an archive member filter, which SUCCEEDS while + # inspecting nothing. A green step that checked no file is the exact + # failure this step exists to prevent. + shopt -s nullglob + zips=(packages/plugin/out/*.zip) + if [ ${#zips[@]} -ne 1 ]; then + echo "::error::expected exactly one ZIP, found ${#zips[@]}: ${zips[*]}" + exit 1 + fi + zip="${zips[0]}" + unzip -l "$zip" > /tmp/zip-list.txt + grep -q "ui/index.html" /tmp/zip-list.txt + if grep -qE "[.]css$" /tmp/zip-list.txt; then + echo "::error::the plugin ZIP contains a stylesheet - plugins ship no CSS" + exit 1 + fi + - name: Upload plugin ZIP uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 5b8029a..6320fa0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ out/ .env .env.local *.log + +# packages/ui builds into packages/plugin/ui — build output that happens to +# live inside a sibling package, and part of the ZIP rather than of the source. +packages/plugin/ui/ diff --git a/docs/iframe-credentials.md b/docs/iframe-credentials.md new file mode 100644 index 0000000..6f21c5f --- /dev/null +++ b/docs/iframe-credentials.md @@ -0,0 +1,168 @@ +# Three things in core that the P2 SPA cannot fix from here + +The port is done and the bundle is green: it typechecks, builds, ships no CSS, +uses only vocabulary classes, and 50 tests pass. None of that proves it +**renders correctly in a browser**, and this file is the honest list of why — +three defects that live in omadia core, on the C8 branch, each of which fails +silently rather than loudly. + +They are ordered by how much they hurt. + +--- + +## 1. The sandbox makes every authenticated API call cross-origin + +**Where:** `web-ui/app/plugin-ui/[pluginId]/_components/PluginUiFrame.tsx` + +```tsx +sandbox="allow-scripts allow-forms allow-popups" +``` + +`allow-same-origin` is absent, deliberately, and the component says why: + +> the bundle is third-party code and this keeps it out of the operator's +> cookies and localStorage on our origin. A plugin needing authenticated calls +> does them from its own backend router, which is where its authentication +> lives anyway. + +The first half is sound. The second half does not follow. A sandbox without +`allow-same-origin` gives the document an **opaque origin**, and the plugin's +"own backend router" is still reached over HTTP from inside that document. So: + +- every `fetch('/bot-api/v1/admin/dev-platform/...')` leaves with `Origin: null` + and is a cross-origin request; +- `credentials: 'include'` cannot attach the session cookie as first-party — a + cross-site request needs `SameSite=None; Secure` on that cookie; +- `EventSource(url, { withCredentials: true })` — the live job-event tail — has + the same problem; +- `localStorage` throws outright in an opaque origin. + +This SPA is **entirely** data-driven. Every one of its four screens opens with a +`GET`. So the current host page renders a correctly-styled, correctly-themed, +correctly-translated shell that shows an error state on all four screens. + +**Why it is not visible in this repo's tests:** they stub `fetch`. A stub has no +origin. This is a property of the browser, not of the client, and only a real +browser against a real host page can show it. + +**The options, honestly:** + +| Option | Cost | +|---|---| +| Add `allow-same-origin` to the sandbox | One word. Gives up the isolation the comment is protecting — the bundle regains access to the operator's cookies on our origin. | +| Keep the sandbox; have core proxy the plugin's API under the frame's own path and answer with permissive CORS for `Origin: null` | Real work, and `Access-Control-Allow-Origin: null` is its own footgun. | +| Serve the bundle from a distinct origin and treat plugins as genuinely third-party | The clean answer. The biggest change. | + +This is a decision about the plugin trust model, not a bug fix, and it belongs +to whoever owns C8. **It is the single thing standing between this bundle and a +working screen.** + +--- + +## 2. The host page rejects every scoped plugin id — including this one + +**Where:** `web-ui/app/plugin-ui/[pluginId]/page.tsx` + +```ts +/** Mirrors the plugin-id charset gate in `manifestLoader`. */ +const PLUGIN_ID = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/; +``` + +It does not mirror it. `manifestLoader.ts:182` is: + +```ts +const PLUGIN_ID_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/; +``` + +The scope group is **optional but blessed**, and every `@omadia/*` plugin uses +one. This plugin's `identity.id` is `@omadia/dev-platform`. The host page's +regex has no scope alternative and no `@` or `/` in its character class, so it +calls `notFound()` on the only id this package can have. + +The nav entry this PR registers is therefore correct and still lands on a 404 +until the regex is fixed. `plugin.ts` percent-encodes the id so the value +survives as one path segment; the remaining half of the fix is one line in core: + +```ts +const PLUGIN_ID = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/; +``` + +Worth checking at the same time that `pluginUiStatic.ts`'s `resolvePackageRoot` +is looked up with the **decoded** id, since Express decodes `:pluginId` for you. + +--- + +## 3. Three vocabulary declarations emit nothing, and `border` is one of them + +**Where:** `web-ui/scripts/plugin-ui.source.css`, lines 341, 346, 353 + +```css +@source inline("border,border-{0,2,4}"); /* 341 */ +@source inline("divide-y,divide-x"); /* 346 */ +@source inline("transition,transition-{none,all,colors,opacity,transform}"); /* 353 */ +``` + +Tailwind's `@source inline()` expands **braces**. A top-level comma is not a +list separator, so all three declarations produce **zero** classes. Verified +against the committed artifact: `middleware/assets/plugin-ui/plugin-ui.css` +contains no `.border`, no `.divide-y`, no `.transition` rule of any kind. + +The neighbouring declarations are fine because they use the empty-alternative +brace form, which is what makes this easy to miss on review: + +```css +@source inline("rounded{,-none,-sm,-md,-lg,-xl,-full}"); /* works */ +@source inline("shadow{,-none,-sm,-md,-lg}"); /* works */ +``` + +**Why it is worse than a missing utility.** Tailwind's base reset is +`border: 0 solid`. So `class="border border-border"` — the single most common +pairing in the ported pages, 27 occurrences — sets a colour on a **zero-width** +border and renders **invisible**. No error, no warning, nothing in any build. +This is precisely the silent-unstyled failure that the whole +no-arbitrary-values contract exists to prevent, sitting inside the artifact +that enforces it. + +`specs/470-dev-platform-plugin/plugin-ui-vocabulary.md` lists `border`, +`divide-y` and `transition` as available, so the document and the generated +sheet disagree. Anyone reading the doc will write a class that does nothing. + +**The fix, in core:** + +```css +@source inline("border{,-0,-2,-4}"); +@source inline("divide-{y,x}"); +@source inline("transition{,-none,-all,-colors,-opacity,-transform}"); +``` + +then `npm run plugin-ui:css` and commit the regenerated artifact. + +**What this package does meanwhile:** `src/lib/cx.ts` exports + +```ts +export const BORDER = 'border-t border-r border-b border-l'; +``` + +The four directional utilities **are** emitted, each setting 1px on its side, so +the rendered box is identical. When core is fixed, `BORDER` collapses to +`'border'` and nothing else changes. `test/vocabulary.test.ts` pins the current +broken reality, so regenerating `vocabulary/classes.txt` after the core fix +fails that test and prompts the collapse rather than leaving the workaround to +rot. + +--- + +## What was verified, and what was not + +| Claim | Evidence | +|---|---| +| Typechecks | `tsc --noEmit`, exit 0 | +| Builds, emits no CSS | `vite build` + `find -name '*.css'` = 0, asserted in CI and in `build-zip.mjs` | +| Uses only served classes | `scripts/check-ui-vocabulary.mjs`, exit 0, 690-class whitelist | +| Rejects a bad class | fixture tests for `w-[137px]`, `[&>tr]:…`, `bg-blue-500` | +| Four screens render from fixtures, en + de, themed | `test/screens.test.tsx`, 9 tests | +| Tests fail when the code breaks | two mutations run, both killed | +| **Renders correctly in a real browser** | **NOT VERIFIED** — blocked on #1 and #2 | + +The last row is the one that matters to an operator, and it stays open until +core moves. Nothing in this repo can close it. diff --git a/package-lock.json b/package-lock.json index df18823..ac7ad1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,500 @@ "node": ">=20" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/css-color/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/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/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/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "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/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-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-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-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/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.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/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.0.tgz", + "integrity": "sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz", + "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -472,453 +966,2577 @@ "node": ">=18" } }, - "node_modules/@omadia/dev-platform": { - "resolved": "packages/plugin", - "link": true - }, - "node_modules/@omadia/dev-platform-plugin-api": { - "resolved": "packages/plugin-api", - "link": true - }, - "node_modules/@omadia/plugin-api": { - "resolved": "../odoo-bot/middleware/packages/plugin-api", - "link": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "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": { - "@types/node": "*" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "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": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.3", + "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/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@omadia/dev-platform": { + "resolved": "packages/plugin", + "link": true + }, + "node_modules/@omadia/dev-platform-plugin-api": { + "resolved": "packages/plugin-api", + "link": true + }, + "node_modules/@omadia/dev-platform-ui": { + "resolved": "packages/ui", + "link": true + }, + "node_modules/@omadia/plugin-api": { + "resolved": "../odoo-bot/middleware/packages/plugin-api", + "link": true + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.5", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.5.tgz", + "integrity": "sha512-FhqjldLTpteueBaKflhNFlMT3+PM0O5fiBUivht6b9CZ1eesJyy7+g3Jr7XwJzt/Hip3ZG5hWwK1MX1FuDiE4w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "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/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "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.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "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/baseline-browser-mapping": { + "version": "2.11.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz", + "integrity": "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "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": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/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/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/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "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/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.411", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", + "integrity": "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/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/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "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/jsdom": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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/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/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "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/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "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/@types/node": { - "version": "25.9.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", - "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "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/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/@types/pg": { - "version": "8.23.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", - "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pg": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz", + "integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/@types/serve-static": { + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz", + "integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-types": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "dev": true, "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, + "split2": "^4.1.0" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "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": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=0.10.0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "xtend": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.10" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "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": ">= 0.6" + "node": ">=6" } }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.6.0" + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, + "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.8" + "node": ">=0.10.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, + "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": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "scheduler": "^0.27.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "react": "^19.2.8" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, "engines": { - "node": ">= 0.4" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" } }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 18" } }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "node": ">=v12.22.7" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, + "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/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "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/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", - "finalhandler": "^2.1.0", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" @@ -928,75 +3546,62 @@ "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", - "on-finished": "^2.4.1", "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "send": "^1.2.0" }, "engines": { - "node": ">= 18.0.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "license": "ISC" }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1005,26 +3610,38 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { + "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { "node": ">= 0.4" }, @@ -1032,697 +3649,1070 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "js-tokens": "^9.0.1" }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "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": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">= 0.8" + "node": ">=12.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "bin": { + "tldts": "bin/cli.js" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=0.6" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=20" } }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "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/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "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": { - "mime-db": "^1.54.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">=18" + "bin": { + "update-browserslist-db": "cli.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "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/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "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/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">= 0.8" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/pg": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz", - "integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==", + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.14.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.16.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.4.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } + "node": ">=18" } }, - "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "optional": true + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/pg-connection-string": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", - "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=4.0.0" + "node": ">=18" } }, - "node_modules/pg-pool": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", - "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/pg-protocol": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz", - "integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==", + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "split2": "^4.1.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 18" + "node": ">=18" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/vite/node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">= 0.4" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, "engines": { - "node": ">= 10.x" + "node": ">=18" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8" + "node": ">=20" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">=18" } }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=20" } }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "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", - "engines": { - "node": ">=18" + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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" + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=14.17" + "node": ">=8" } }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, - "license": "ISC" + "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", @@ -1734,6 +4724,13 @@ "node": ">=0.4" } }, + "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/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", @@ -1746,7 +4743,7 @@ }, "packages/plugin": { "name": "@omadia/dev-platform", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "devDependencies": { "@omadia/dev-platform-plugin-api": "*", @@ -1774,6 +4771,30 @@ "engines": { "node": ">=20" } + }, + "packages/ui": { + "name": "@omadia/dev-platform-ui", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "jsdom": "^27.0.0", + "typescript": "^6.0.2", + "vite": "^7.1.0", + "vitest": "^3.2.0" + }, + "engines": { + "node": ">=20" + } } } } diff --git a/package.json b/package.json index fe79111..09ccd7c 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,15 @@ ], "//typecheck": "packages/plugin resolves @omadia/dev-platform-plugin-api through its emitted .d.ts, so the API package is BUILT before anything is type-checked. You cannot type-check against declarations that do not exist yet.", "scripts": { - "build": "npm run build -w packages/plugin-api && npm run build -w packages/plugin", + "build": "npm run build -w packages/plugin-api && npm run build -w packages/plugin && npm run build -w packages/ui", "typecheck": "npm run build -w packages/plugin-api && npm run typecheck --workspaces --if-present", - "test": "npm run test -w packages/plugin", + "test": "npm run test -w packages/plugin && npm run test -w packages/ui", "package": "npm run package -w packages/plugin", "clean": "npm run clean --workspaces --if-present && rm -rf node_modules", "link:core": "node scripts/link-core.mjs", "codegen:migrations": "npm run codegen:migrations -w packages/plugin" }, - "//esbuild": "The sibling plugin repos pin ^0.24.0; this one does not. esbuild <=0.24.2 carries GHSA-67mh-4wv8-2f99 (the dev server answers cross-origin requests), and a brand-new public repo should not ship with a known advisory. We only ever call the build() API from scripts/test.mjs \u2014 never serve() \u2014 so the advisory does not reach us either way, but the fix is free.", + "//esbuild": "The sibling plugin repos pin ^0.24.0; this one does not. esbuild <=0.24.2 carries GHSA-67mh-4wv8-2f99 (the dev server answers cross-origin requests), and a brand-new public repo should not ship with a known advisory. We only ever call the build() API from scripts/test.mjs — never serve() — so the advisory does not reach us either way, but the fix is free.", "devDependencies": { "typescript": "^6.0.2", "@types/node": "^25.6.0", @@ -35,5 +35,6 @@ }, "allowScripts": { "esbuild@0.25.12": true - } + }, + "//ui": "packages/ui builds INTO packages/plugin/ui, so it must run after the plugin build and before `package`. build-zip.mjs lists `ui` in REQUIRED_DIRS: a ZIP cut without it installs, activates, adds a nav entry, and 404s when the operator clicks it." } diff --git a/packages/plugin/scripts/build-zip.mjs b/packages/plugin/scripts/build-zip.mjs index f7fd85b..4a6e1a8 100644 --- a/packages/plugin/scripts/build-zip.mjs +++ b/packages/plugin/scripts/build-zip.mjs @@ -15,7 +15,8 @@ * Adapted from `omadia-integration-odoo/scripts/build-zip.mjs`. The differences * are the workspace layout (this package sits under `packages/plugin`, so the * script resolves paths from its own location, not from an assumed CWD) and the - * `packages/ui` payload, which is not built yet. + * `ui/` payload — the compiled operator SPA that `packages/ui` builds into this + * package (epic #470 P2). * * ## It does NOT bundle * @@ -60,10 +61,31 @@ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); * of this script shipped exactly that ZIP. */ const REQUIRED_FILES = ['manifest.yaml']; -const REQUIRED_DIRS = ['dist', 'migrations']; +const REQUIRED_DIRS = ['dist', 'migrations', 'ui']; const OPTIONAL_FILES = ['README.md', 'LICENSE', 'NOTICE']; const OPTIONAL_DIRS = ['assets', 'skills']; +/** + * `ui/` is REQUIRED, alongside `dist` and `migrations`, and the reasoning is + * the same one that made `migrations/` required after the first cut of this + * script shipped without it. + * + * `activate()` registers a nav entry pointing at `/plugin-ui/`, which core + * renders as an iframe onto `/p//ui/index.html`. A ZIP cut without `ui/` + * therefore installs cleanly, activates cleanly, adds a nav entry to the + * operator's sidebar — and answers 404 when they click it. Optional would mean + * "a build that forgot to run `vite build` ships silently"; required means it + * fails here, where the fix is one command. + * + * The directory is produced by `npm run build -w packages/ui`, which the root + * `build` script runs after the plugin's `tsc`. It is gitignored: it is build + * output that happens to live inside a sibling package. + */ +const UI_DIR = 'ui'; + +/** The bundle entry. Its absence means `vite build` did not finish. */ +const REQUIRED_IN_UI = ['index.html']; + /** The manifest's `lifecycle.entry`. Its absence means `tsc` did not finish. */ const REQUIRED_IN_DIST = ['plugin.js']; @@ -162,6 +184,40 @@ if (readdirSync(join(stageDir, 'migrations')).some((f) => f.endsWith('.sql'))) { } console.log(` + migrations/ verified (${stagedMigrations.length} codegen'd + checksums.json)`); +// --- ui/ sanity ----------------------------------------------------------- +// Two properties the archive must have, checked here because both fail +// SILENTLY at runtime and neither is visible in a directory listing. +{ + const uiRoot = join(stageDir, UI_DIR); + for (const rel of REQUIRED_IN_UI) { + if (!existsSync(join(uiRoot, rel))) { + throw new Error( + `ui/${rel} is missing — run \`npm run build -w packages/ui\` before packaging`, + ); + } + } + + // No stylesheet, ever. `.css` is absent from the ZIP extension allowlist, + // so a bundle that emitted one is rejected at ingest with + // `zip.forbidden_extension` — after upload, by someone else, with a message + // that does not name this build. Catching it here names it. + const offenders = []; + const scan = (dir, prefix) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const rel = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) scan(join(dir, entry.name), rel); + else if (entry.name.toLowerCase().endsWith('.css')) offenders.push(rel); + } + }; + scan(uiRoot, ''); + if (offenders.length > 0) { + throw new Error( + `ui/ contains ${offenders.length} stylesheet(s) — ${offenders.join(', ')}. ` + + 'Plugins ship no CSS; the bundle links the sheet core serves. See packages/ui/vocabulary/README.md.', + ); + } +} + // --- package.json, without devDependencies --------------------------------- // devDependencies are meaningless inside a published artifact — nothing ever // installs them from a plugin ZIP — and in this repo they point at a sibling diff --git a/packages/plugin/src/plugin.ts b/packages/plugin/src/plugin.ts index 971878d..18b3376 100644 --- a/packages/plugin/src/plugin.ts +++ b/packages/plugin/src/plugin.ts @@ -400,10 +400,26 @@ async function activateInner( // PR #536 registered this from `index.ts` behind `DEV_PLATFORM_ENABLED`, // deliberately temporary, to prove the loop before any code moved. This is // the call it was always going to become; nothing about the shell changes. + // + // THE HREF MOVED IN P2, and leaving it at the old path would be the + // quietest possible way to break this plugin. `/admin/dev-platform` was a + // page COMPILED INTO web-ui. P2 ports those pages out of core into + // `packages/ui`, so core deletes that route — and a nav entry still aimed + // at it renders a sidebar link to the shell's 404, with nothing in any + // build to say so. `/plugin-ui/` is the generic host page core added + // in C8: it validates the id and iframes + // `/p//ui/index.html?theme=&palette=&locale=`, which is where the + // `ui/` directory in this package's ZIP is served from. + // + // `encodeURIComponent` is load-bearing, not defensive. This plugin's id is + // SCOPED — `@omadia/dev-platform`, per `manifest.yaml` and per the charset + // `manifestLoader.ts:182` blesses — so it contains a `/`. Interpolated raw + // it would emit `/plugin-ui/@omadia/dev-platform`: two path segments, which + // neither the Next dynamic segment nor Express's `:pluginId` can match. disposers.push( ctx.uiRoutes.registerNav({ navId: 'devPlatform', - href: '/admin/dev-platform', + href: `/plugin-ui/${encodeURIComponent(DEV_PLATFORM_PLUGIN_ID)}`, cluster: 'adminCluster', order: 50, label: { en: 'Dev Platform', de: 'Dev-Plattform' }, diff --git a/packages/ui/README.md b/packages/ui/README.md index c452f85..9409d6f 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -1,25 +1,88 @@ -# `packages/ui` — placeholder +# `@omadia/dev-platform-ui` — the operator SPA -The Vite SPA port lands in **P2**. +The Dev Platform's four operator screens, as a standalone Vite/React bundle. +Built into `../plugin/ui/`, shipped inside the plugin ZIP, served by core at +`/p//ui/` and embedded by web-ui's `/plugin-ui/` page +(epic byte5ai/omadia#470, P2 against contract C8). -Twenty-six `'use client'` pages currently live in omadia core's `web-ui` under -the dev-platform routes. P2 ports them to a standalone Vite/React SPA, replaces -`next-intl` with local i18n, and constrains Tailwind to the vocabulary that core -serves to plugins. +```sh +npm run build # vite build -> ../plugin/ui, then the vocabulary gate +npm run typecheck +npm test # vitest: 50 tests +npm run lint:vocabulary +``` -P2 is blocked on **C8** in core: a distributed plugin cannot ship a multi-file -SPA under today's contract, which mandates single-file HTML and a `tsc`-only -build. C8 extracts the `@theme inline` bridge out of `globals.css`, generates and -serves the plugin Tailwind subset, and adds static-asset serving. C8 is also the -epic's abandonment checkpoint — if it proves too costly, the fallback is an -npm-published UI package that `web-ui` optionally installs. +## Why this package exists -Two regressions the port has to handle, already identified and easy to miss: +The pages used to be `web-ui/app/admin/dev-platform/**` — compiled into core. +A plugin that lives in its own repository cannot compile pages into core's +build without becoming a hardcoded core reference, which the epic forbids. So +the UI ships as a compiled bundle inside the package and core serves it. -- `next/font` does not cross an iframe boundary — the plugin SPA renders in the - fallback stack unless it ships its own `@font-face`. -- `data-theme` does not cross it either — the plugin UI sits in light mode inside - a shell the operator forced dark. Fixed by a core host page passing - `?theme=&locale=`. +That trade is what every constraint below comes from. -See `specs/470-dev-platform-plugin/plan.md` §4.3 and §4.3a in the omadia repo. +## The four screens + +| Fragment | Screen | +|---|---| +| `#/` | Hub — repos / jobs / apps / gates, tab deep-linked via `#/?tab=` | +| `#/jobs/` | Job detail — phase rail, live SSE log, artifacts, gates | +| `#/repos/` | Repo detail — budget, webhook, bind GitHub App | +| `#/repos/new` | Add-repo wizard — device flow, credential, checks | + +Routing is by **fragment**, not path. `pluginUiStatic.ts` serves exactly two +shapes — the bundle root and a real file — so a client route in the path would +404 on reload; a fragment never reaches the server. It also avoids needing to +know the plugin id at build time, since the id comes from the install. + +## What replaced what + +| Core | Here | Why | +|---|---|---| +| `next-intl` | `src/lib/i18n.tsx` | No Next request context inside an iframe. 300 keys per locale, plain `{name}` interpolation, no ICU parser. The three ICU plurals were de-sugared to `{ one, other }` at extraction. | +| `next/link`, `next/navigation` | `src/lib/router.tsx` | Hash router, ~180 lines, same hook names so call sites are unchanged. | +| `@/app/_lib/api` (4,827 lines) | `src/lib/apiError.ts` | Exactly one name was imported from it: `ApiError`. | +| `framer-motion` | — | Animated `scale`/`y`, neither of which is in the vocabulary. 40 KB for two properties that cannot be expressed. | +| `lucide-react` | inline `` | One 16px chevron. | +| `Button`, `ConfirmDialog` | rewritten | Core writes every variant as an arbitrary value (`bg-[color:var(--accent)]`). Ingest rejects that shape; the vocabulary token `bg-accent` resolves to the same variable. | + +`DevJobChatCard` and `devJobChatCardState` are **not** ported. That card renders +inside core's chat transcript, not in this iframe; `plan.md` §4.3 excludes the +chat surface from the compiled-SPA option (it is H3, still undecided). The one +function `JobDetailScreen` needed from it — `findGateForJob`, five lines — is in +`src/lib/gates.ts`. + +## Two hard rules + +**1. No CSS. Ever.** This package imports no stylesheet and Vite emits none. +`.css` is absent from the plugin-ZIP extension allowlist and from the static +router's Content-Type table, permanently — that absence is what forces every +plugin onto the one sheet core generates from its own Lume tokens. `index.html` +links `/bot-api/_harness/plugin-ui.css` and that is the whole styling channel. +Enforced in three places: `cssCodeSplit: false`, `check-ui-vocabulary.mjs`, and +an assertion in `build-zip.mjs`. + +**2. Only classes in `vocabulary/classes.txt`.** 690 of them, extracted from the +generated stylesheet itself. A class outside the set does not error — it renders +**unstyled**, on the operator's screen, and nowhere else. See +`vocabulary/README.md`. + +Never build a class from a template literal. `` `bg-${tone}` `` defeats every +static check here and core's alike. Write the branches out. + +## No inline script in `index.html` + +Core's proof fixture sets `data-theme` from an inline ` + + + Dev Platform + + + + + + + + +
+ + diff --git a/packages/ui/messages/de.json b/packages/ui/messages/de.json new file mode 100644 index 0000000..91216f9 --- /dev/null +++ b/packages/ui/messages/de.json @@ -0,0 +1,398 @@ +{ + "adminDevPlatform": { + "title": "Dev-Plattform", + "intro": "Repositories, Dev-Jobs und Pull-Request-Pipelines, ausgeführt von omadia-Agenten. Jobs laufen isoliert und enden immer in einem Pull Request zur menschlichen Prüfung — omadia merged nie.", + "tabs": { + "repos": "Repositories", + "jobs": "Jobs", + "apps": "GitHub Apps", + "gates": "Freigaben" + }, + "apps": { + "createHeading": "GitHub App erstellen", + "createBody": "Eine GitHub App gibt der Dev-Plattform kurzlebige, repository-spezifische Tokens, die nicht mergen können — der stärkste Credential-Modus. Beim Erstellen leiten wir dich zu GitHub, wo du das Manifest bestätigst, und danach wieder zurück.", + "orgLabel": "Organisation (optional)", + "orgPlaceholder": "acme", + "orgHelp": "Leer lassen, um die App unter deinem persönlichen Konto zu erstellen. Mit Organisation wird sie dort angelegt.", + "create": "GitHub App erstellen", + "creating": "GitHub wird geöffnet", + "createError": "Das App-Setup konnte nicht gestartet werden. Versuch es erneut.", + "listHeading": "Registrierte Apps", + "loading": "Lädt…", + "unauthorized": "Du brauchst Operator-Zugriff für die Dev-Plattform.", + "loadError": "Beim Laden ist etwas schiefgelaufen.", + "retry": "Erneut versuchen", + "empty": "Noch keine GitHub Apps. Erstell eine, um Repositories mit Scoped Tokens zu verbinden.", + "colOwner": "Owner", + "colSlug": "App", + "colInstalls": "Installationen", + "installs": { + "one": "{count} Installation", + "other": "{count} Installationen" + }, + "openOnGithub": "Auf GitHub öffnen" + }, + "gates": { + "advisoryHeading": "Plan-Freigabe ist beratend", + "advisoryBody": "Eine Plan-Freigabe lässt den Job nur mit der Umsetzung beginnen. Die maßgebliche Sicherheitsprüfung ist das Diff-Gate, das den tatsächlichen Patch vor jedem Pull Request prüft. omadia merged nie.", + "loading": "Lädt…", + "unauthorized": "Du brauchst Operator-Zugriff für die Dev-Plattform.", + "loadError": "Beim Laden ist etwas schiefgelaufen.", + "retry": "Erneut versuchen", + "empty": "Es warten keine Pläne auf Freigabe.", + "job": "Job", + "deadline": "Frist {at}", + "noDeadline": "Keine Frist", + "plan": "Plan", + "viewPlan": "Plan ansehen", + "noPlan": "Kein Plan-Artefakt", + "planLoading": "Plan wird geladen…", + "planLoadError": "Der Plan konnte nicht geladen werden.", + "holders": "Holder", + "noHolders": "keine", + "questions": "Rückfragen", + "answerPlaceholder": "Deine Antwort", + "noQuestions": "Der Agent hatte keine Rückfragen.", + "noteLabel": "Notiz (optional)", + "notePlaceholder": "Kontext zur Entscheidung", + "approve": "Plan freigeben", + "approving": "Wird freigegeben", + "reject": "Ablehnen", + "rejecting": "Wird abgelehnt", + "notHolder": "Du bist nicht berechtigt, dieses Gate aufzulösen. Die Holder-Rolle liegt inzwischen bei jemand anderem.", + "alreadyResolved": "Dieses Gate ist nicht mehr offen — es wurde aufgelöst oder ist abgelaufen. Die Liste wurde aktualisiert.", + "resolveError": "Das Gate konnte nicht aufgelöst werden. Versuch es erneut." + }, + "bindApp": { + "intro": "Verbinde dieses Repository mit einer GitHub-App-Installation. Die App stellt Scoped, kurzlebige Tokens aus, die nicht mergen können. omadia prüft, dass die Installation dieses Repository abdeckt, bevor gespeichert wird.", + "loadingApps": "Apps werden geladen", + "noApps": "Noch keine GitHub Apps. Erstell zuerst eine im Tab „GitHub Apps“.", + "installLink": "Installieren / verwalten", + "installationLabel": "Installations-ID", + "installationPlaceholder": "12345678", + "installationHelp": "Installiere die App auf diesem Repository und füge dann die Installations-ID ein, die GitHub nach der Installation anzeigt.", + "bind": "GitHub App verbinden", + "binding": "Wird verbunden", + "bound": "GitHub App verbunden. Für dieses Repository werden jetzt Scoped Tokens verwendet.", + "boundWithWarnings": "Verbunden mit Warnungen", + "errors": { + "notCovering": "Diese Installation deckt dieses Repository nicht ab. Installiere die App auf diesem Repository und verwende dann dessen Installations-ID.", + "unknownInstallation": "Diese Installation ist nicht registriert. Schließe die App-Installation ab, damit omadia sie erfasst, und versuch es erneut.", + "invalidInstallation": "Gib die Installations-ID ein, die GitHub nach der Installation der App angezeigt hat.", + "appUnusable": "Die App hinter dieser Installation ist unbrauchbar — ihre Credentials fehlen. Erstelle die App neu.", + "generic": "Die GitHub App konnte nicht verbunden werden. Versuch es erneut." + } + }, + "loading": "Lädt…", + "loadError": "Beim Laden ist etwas schiefgelaufen.", + "unauthorized": "Du brauchst Operator-Zugriff für die Dev-Plattform.", + "retry": "Erneut versuchen", + "repos": { + "count": { + "one": "{count} Repository", + "other": "{count} Repositories" + }, + "name": "Repository", + "forge": "Forge", + "credential": "Credential-Modus", + "branch": "Default-Branch", + "protectionCol": "Protection", + "credentialExpired": "Credential abgelaufen", + "credentialModes": { + "githubApp": "GitHub App", + "deviceFlow": "Device Flow — User-Token", + "pat": "Personal Access Token" + }, + "newJob": "Neuer Job", + "settings": "Einstellungen", + "add": "Repository hinzufügen", + "empty": { + "heading": "Noch keine Repositories", + "body": "Die Dev-Plattform führt isolierte Jobs gegen deine Repositories aus: ein Issue analysieren, planen, implementieren und einen Pull Request zur menschlichen Prüfung öffnen. omadia merged nie — das bleibt bei dir.", + "cta": "Repository hinzufügen" + }, + "remove": { + "action": "Entfernen", + "title": "Repository entfernen?", + "body": "Laufende Jobs werden zuerst abgebrochen. Das gespeicherte Credential dieses Repositories wird aus dem Vault gelöscht.", + "confirm": "Repository entfernen", + "cancel": "Repository behalten" + }, + "protection": { + "protected": "geschützt", + "unprotected": "ungeschützt", + "unchecked": "ungeprüft", + "recheck": "Neu prüfen", + "rechecking": "Prüft", + "warning": "Ohne Branch Protection könnte dein Token direkt auf den Default-Branch pushen. Die No-Merge-Garantie hängt daran." + } + }, + "jobs": { + "job": "Job", + "repo": "Repo", + "kind": "Art", + "phase": "Phase", + "status": "Status", + "cost": "Kosten", + "costEstimatedTitle": "Geschätzte Kosten (Abo-CLI – nicht gemessen)", + "costEstimatedTag": "geschätzt", + "costNearTitle": "Nahe am Budget (≥80%)", + "costOverTitle": "Budget überschritten (≥100%)", + "age": "Alter", + "view": "Ansehen", + "cancel": "Abbrechen", + "delete": "Löschen", + "empty": "Noch keine Jobs. Starte einen aus einer Repository-Zeile.", + "live": "live", + "liveLost": "Verbindung verloren — versucht erneut", + "filters": { + "allRepos": "Alle Repositories", + "allStatuses": "Alle Status" + }, + "statuses": { + "queued": "wartet", + "provisioning": "provisioniert", + "running": "läuft", + "waiting": "wartet auf dich", + "applying": "wendet an", + "done": "fertig", + "failed": "fehlgeschlagen", + "cancelled": "abgebrochen", + "stalled": "hängt", + "budgetExceeded": "Budget überschritten" + }, + "kinds": { + "analyze": "Analysieren", + "fixIssue": "Issue fixen", + "implement": "Implementieren" + }, + "cancelConfirm": { + "title": "Job abbrechen?", + "body": "Der Runner wird beendet. Branch, Log und ein bereits hochgeladener Diff bleiben erhalten.", + "confirm": "Job abbrechen", + "cancel": "Weiterlaufen lassen" + }, + "deleteConfirm": { + "title": "Job löschen?", + "body": "Der Job, sein Log und seine Artefakte werden endgültig entfernt. Ein bereits erstellter Branch oder PR bleibt erhalten.", + "confirm": "Job löschen", + "cancel": "Behalten" + } + }, + "newJob": { + "title": "Neuer Job für {repo}", + "kind": "Art", + "kinds": { + "fixIssue": "Ein Issue fixen", + "analyze": "Analysieren", + "implement": "Implementieren" + }, + "fromIssue": "Aus Issue", + "fromBrief": "Freitext-Brief", + "loadingIssues": "Lädt offene Issues", + "noIssues": "Keine offenen Issues in diesem Repository gefunden.", + "issue": "Issue", + "selectIssue": "Issue auswählen", + "brief": "Brief", + "briefPlaceholder": "Was soll der Agent tun?", + "error": "Der Job konnte nicht angelegt werden.", + "cancel": "Abbrechen", + "starting": "Startet", + "start": "Job starten" + }, + "wizard": { + "title": "Repository hinzufügen", + "intro": "Verbinde ein Repository, damit die Dev-Plattform isolierte Jobs dagegen ausführen und Pull Requests zu deiner Prüfung öffnen kann.", + "backToRepos": "Zurück zu den Repositories", + "steps": { + "repo": "Repository", + "credentials": "Credentials", + "confirm": "Bestätigen" + }, + "fields": { + "forge": "Forge", + "owner": "Owner", + "name": "Repository-Name", + "branch": "Default-Branch" + }, + "next": "Weiter", + "back": "Zurück", + "finish": "Repository hinzufügen", + "adding": "Fügt hinzu", + "edit": "Bearbeiten", + "summary": { + "github_app": "GitHub App", + "device_flow": "Device Flow", + "pat": "Personal Access Token" + }, + "error": { + "submit": "Das Repository konnte nicht hinzugefügt werden. Prüfe Owner, Name und Credential und versuche es erneut." + }, + "done": { + "heading": "Repository hinzugefügt", + "body": "{repo} ist verbunden. Du kannst aus der Repository-Liste einen Job starten.", + "toList": "Zu den Repositories" + }, + "credentials": { + "groupLabel": "Credential-Modus", + "githubApp": { + "title": "GitHub App — empfohlen", + "soon": "bald verfügbar", + "body": "Installation pro Repository, kurzlebige, eng gescopte Tokens, Commits als omadia-dev[bot]. Das Token kann nicht mergen — strukturell." + }, + "deviceFlow": { + "title": "Device Flow — Schnellstart", + "body": "Melde dich per einmaligem Device-Code mit deinem GitHub-Account an. Der schnellste Weg, die Dev-Plattform auszuprobieren." + }, + "deviceTradeoffs": { + "heading": "Was dieser Modus bedeutet", + "asUser": "Commits erscheinen als dein GitHub-User, nicht als Bot", + "repoWide": "das Token gewährt Zugriff auf alle deine Repositories, nicht nur dieses", + "canMerge": "das Token kann mergen — die No-Merge-Regel setzt nur die omadia-Policy durch, nicht der Token-Scope", + "noWebhooks": "Webhook-Trigger bleiben für dieses Repository deaktiviert" + }, + "device": { + "codeAria": "Device-Code {code}", + "copyCode": "Code kopieren", + "copied": "Kopiert", + "waiting": "Wartet auf Autorisierung", + "authorizedAs": "Autorisiert als {login}", + "expired": "Code abgelaufen", + "error": "Autorisierung fehlgeschlagen", + "restart": "Neu starten" + }, + "pat": { + "title": "Fine-grained PAT / Deploy-Key", + "body": "Füge ein fine-grained Personal Access Token ein, das auf dieses Repository gescopt ist. Auch der Weg für GitLab und Gitea.", + "label": "Token", + "placeholder": "github_pat_…" + } + }, + "checks": { + "label": "Branch Protection auf {branch}", + "enabled": "aktiv", + "missing": "fehlt", + "unknown": "nicht prüfbar", + "warning": "Ohne Branch Protection könnte ein Device-Flow- oder PAT-Token direkt auf {branch} pushen. Aktiviere sie in den Repository-Einstellungen — die Dev-Plattform pusht nur auf omadia/job-*-Branches, aber Protection macht das strukturell.", + "unknownHint": "Das gespeicherte Token kann die Protection-Einstellungen nicht lesen — klassischen Device-Flow-Tokens fehlt meist der Admin-Lesezugriff. Du kannst jederzeit neu prüfen." + } + }, + "detail": { + "jobLabel": "Job {hash}", + "loading": "Lädt…", + "notFound": "Diesen Job gibt es nicht.", + "railLabel": "Pipeline-Phasen", + "phases": { + "analyze": "Analyse", + "bootstrap": "Bootstrap", + "plan": "Planung", + "clarify": "Klärung", + "gate": "Gate", + "implement": "Umsetzung", + "review": "Review", + "pr": "PR" + }, + "phaseSkipped": "übersprungen — keine Fragen", + "toolCall": { + "pending": "läuft", + "failed": "fehlgeschlagen", + "noOutput": "(keine Ausgabe)", + "prompt": "Prompt", + "result": "Ergebnis", + "output": "Ausgabe", + "moreDiffLines": { + "one": "… {count} weitere Zeile", + "other": "… {count} weitere Zeilen" + } + }, + "openPr": "Pull Request öffnen", + "artifactError": "Das Ergebnis dieser Phase konnte nicht geladen werden.", + "logEmpty": "Noch keine Log-Ausgabe.", + "scrollToBottom": "Nach unten scrollen", + "connection": { + "live": "live · letztes Event vor {seconds}s", + "reconnecting": "verbindet neu", + "closed": "Stream beendet — Job abgeschlossen" + }, + "cancel": { + "action": "Abbrechen", + "title": "Job abbrechen?", + "body": "Der Runner wird beendet, der Branch bleibt erhalten.", + "confirm": "Job abbrechen", + "cancelLabel": "Weiterlaufen lassen" + }, + "delete": { + "action": "Löschen", + "title": "Job löschen?", + "body": "Der Job, sein Log und seine Artefakte werden endgültig entfernt. Ein bereits erstellter Branch oder PR bleibt erhalten.", + "confirm": "Job löschen", + "cancelLabel": "Behalten" + }, + "sidebar": { + "backend": "Backend", + "agent": "Agent", + "branch": "Branch", + "source": "Quelle", + "createdBy": "Angelegt von", + "tokens": "Tokens ein / aus", + "cost": "Kosten" + } + }, + "repoDetail": { + "back": "Zurück zu den Repositories", + "loading": "Lädt…", + "loadError": "Dieses Repository konnte nicht geladen werden.", + "forge": "Forge", + "branch": "Default-Branch", + "credential": "Credential", + "credentialHeading": "Credential", + "credentialCurrent": "Aktueller Modus: {kind}", + "runsTests": "Führt Tests aus", + "yes": "ja", + "no": "nein", + "protectionHeading": "Branch Protection", + "recheck": "Neu prüfen", + "rechecking": "Prüft", + "budget": { + "heading": "Budget", + "help": "Begrenze die Kosten pro Job für dieses Repository. Leer lassen, um den Plattform-Standard zu verwenden.", + "costLabel": "Kostenbudget (USD pro Job)", + "costPlaceholder": "z. B. 5.00", + "costError": "Gib einen positiven Betrag ein oder lass das Feld für den Standard leer.", + "save": "Budget speichern", + "saving": "Speichert", + "saved": "Gespeichert", + "saveError": "Das Budget konnte nicht gespeichert werden." + }, + "webhook": { + "heading": "Webhook-Auslöser", + "help": "Wenn aktiviert, startet das Anwenden des Trigger-Labels an einem Issue einen Job — aber nur für einen freigegebenen Absender. Eine leere Absenderliste hält den Webhook aus.", + "enable": "Aktivieren", + "disable": "Deaktivieren", + "saving": "Speichert", + "statusLabel": "Status", + "enabledStatus": "Aktiviert", + "disabledStatus": "Deaktiviert", + "triggerLabelLabel": "Trigger-Label", + "sendersLabel": "Erlaubte Absender", + "sendersEmpty": "Keine — Webhook-Auslöser sind aus, bis ein Absender hinzugefügt wird.", + "saveError": "Die Webhook-Einstellung konnte nicht gespeichert werden." + } + } + }, + "chat": { + "devJob": { + "heading": "Dev-Job", + "viewJob": "Job öffnen", + "viewPr": "PR ansehen", + "connectionLost": "Verbindung verloren", + "gate": { + "title": "Freigabe erforderlich", + "approve": "Freigeben", + "reject": "Ablehnen", + "resolving": "Wird gesendet…", + "error": "Gate konnte nicht aufgelöst werden." + } + } + } +} diff --git a/packages/ui/messages/en.json b/packages/ui/messages/en.json new file mode 100644 index 0000000..ab1064b --- /dev/null +++ b/packages/ui/messages/en.json @@ -0,0 +1,398 @@ +{ + "adminDevPlatform": { + "title": "Dev Platform", + "intro": "Repositories, dev jobs, and pull-request pipelines run by omadia agents. Jobs run isolated and always end in a pull request for human review — omadia never merges.", + "tabs": { + "repos": "Repositories", + "jobs": "Jobs", + "apps": "GitHub Apps", + "gates": "Approvals" + }, + "apps": { + "createHeading": "Create a GitHub App", + "createBody": "A GitHub App gives the dev platform short-lived, per-repository tokens that cannot merge — the strongest credential mode. Creating one sends you to GitHub to approve the manifest, then brings you back.", + "orgLabel": "Organization (optional)", + "orgPlaceholder": "acme", + "orgHelp": "Leave empty to create the App under your personal account. Set an organization to create it there instead.", + "create": "Create GitHub App", + "creating": "Opening GitHub", + "createError": "The App setup could not be started. Try again.", + "listHeading": "Registered Apps", + "loading": "Loading", + "unauthorized": "you need operator access for the dev platform.", + "loadError": "Something went wrong while loading.", + "retry": "Retry", + "empty": "No GitHub Apps yet. Create one to bind repositories with scoped tokens.", + "colOwner": "Owner", + "colSlug": "App", + "colInstalls": "Installations", + "installs": { + "one": "{count} installation", + "other": "{count} installations" + }, + "openOnGithub": "Open on GitHub" + }, + "gates": { + "advisoryHeading": "Plan approval is advisory", + "advisoryBody": "Approving a plan only lets the job start implementing. The authoritative safety check is the diff gate, which reviews the actual patch before any pull request. omadia never merges.", + "loading": "Loading", + "unauthorized": "you need operator access for the dev platform.", + "loadError": "Something went wrong while loading.", + "retry": "Retry", + "empty": "No plans are waiting for approval.", + "job": "Job", + "deadline": "Deadline {at}", + "noDeadline": "No deadline", + "plan": "Plan", + "viewPlan": "View plan", + "noPlan": "No plan artifact", + "planLoading": "Loading plan…", + "planLoadError": "The plan could not be loaded.", + "holders": "Holders", + "noHolders": "none", + "questions": "Questions", + "answerPlaceholder": "Your answer", + "noQuestions": "The agent asked no questions.", + "noteLabel": "Note (optional)", + "notePlaceholder": "Context for the decision", + "approve": "Approve plan", + "approving": "Approving", + "reject": "Reject", + "rejecting": "Rejecting", + "notHolder": "You are not authorized to resolve this gate. Its holder role has moved to someone else.", + "alreadyResolved": "This gate is no longer pending — it was resolved or expired. The list has been refreshed.", + "resolveError": "The gate could not be resolved. Try again." + }, + "bindApp": { + "intro": "Bind this repository to a GitHub App installation. The App issues scoped, short-lived tokens that cannot merge. omadia verifies the installation covers this repository before saving.", + "loadingApps": "Loading Apps", + "noApps": "No GitHub Apps yet. Create one from the GitHub Apps tab first.", + "installLink": "Install / manage", + "installationLabel": "Installation ID", + "installationPlaceholder": "12345678", + "installationHelp": "Install the App on this repository, then paste the installation ID GitHub shows after install.", + "bind": "Bind GitHub App", + "binding": "Binding", + "bound": "GitHub App bound. Scoped tokens are now used for this repository.", + "boundWithWarnings": "Bound with warnings", + "errors": { + "notCovering": "That installation does not cover this repository. Install the App on this repository, then use its installation ID.", + "unknownInstallation": "That installation is not registered. Complete the App install so omadia records it, then try again.", + "invalidInstallation": "Enter the installation ID GitHub showed after installing the App.", + "appUnusable": "The App backing this installation is unusable — its credentials are missing. Re-create the App.", + "generic": "The GitHub App could not be bound. Try again." + } + }, + "loading": "Loading", + "loadError": "Something went wrong while loading.", + "unauthorized": "you need operator access for the dev platform.", + "retry": "Retry", + "repos": { + "count": { + "one": "{count} repository", + "other": "{count} repositories" + }, + "name": "Repository", + "forge": "Forge", + "credential": "Credential mode", + "branch": "Default branch", + "protectionCol": "Protection", + "credentialExpired": "credential expired", + "credentialModes": { + "githubApp": "GitHub App", + "deviceFlow": "Device flow — user token", + "pat": "Personal access token" + }, + "newJob": "New job", + "settings": "Settings", + "add": "Add repository", + "empty": { + "heading": "No repositories yet", + "body": "the dev platform runs isolated jobs against your repositories: analyze an issue, plan, implement, and open a pull request for human review. omadia never merges — that stays with you.", + "cta": "Add repository" + }, + "remove": { + "action": "Remove", + "title": "Remove repository?", + "body": "Running jobs are cancelled first. The stored credential for this repository is deleted from the vault.", + "confirm": "Remove repository", + "cancel": "Keep repository" + }, + "protection": { + "protected": "protected", + "unprotected": "unprotected", + "unchecked": "unchecked", + "recheck": "Re-check", + "rechecking": "Checking", + "warning": "Without branch protection your token could push to the default branch directly. The no-merge guarantee depends on it." + } + }, + "jobs": { + "job": "Job", + "repo": "Repo", + "kind": "Kind", + "phase": "Phase", + "status": "Status", + "cost": "Cost", + "costEstimatedTitle": "Estimated cost (subscription CLI — not metered)", + "costEstimatedTag": "est.", + "costNearTitle": "Near budget (≥80%)", + "costOverTitle": "Over budget (≥100%)", + "age": "Age", + "view": "View", + "cancel": "Cancel", + "delete": "Delete", + "empty": "No jobs yet. Start one from a repository row.", + "live": "live", + "liveLost": "connection lost — retrying", + "filters": { + "allRepos": "All repositories", + "allStatuses": "All statuses" + }, + "statuses": { + "queued": "queued", + "provisioning": "provisioning", + "running": "running", + "waiting": "waiting", + "applying": "applying", + "done": "done", + "failed": "failed", + "cancelled": "cancelled", + "stalled": "stalled", + "budgetExceeded": "budget exceeded" + }, + "kinds": { + "analyze": "Analyze", + "fixIssue": "Fix issue", + "implement": "Implement" + }, + "cancelConfirm": { + "title": "Cancel job?", + "body": "The runner is terminated. The branch, the log, and any uploaded diff are kept.", + "confirm": "Cancel job", + "cancel": "Keep running" + }, + "deleteConfirm": { + "title": "Delete job?", + "body": "The job, its log, and its artifacts are removed permanently. Any branch or PR it created is kept.", + "confirm": "Delete job", + "cancel": "Keep it" + } + }, + "newJob": { + "title": "New job for {repo}", + "kind": "Kind", + "kinds": { + "fixIssue": "Fix an issue", + "analyze": "Analyze", + "implement": "Implement" + }, + "fromIssue": "From issue", + "fromBrief": "Free-text brief", + "loadingIssues": "Loading open issues", + "noIssues": "No open issues found for this repository.", + "issue": "Issue", + "selectIssue": "Select an issue", + "brief": "Brief", + "briefPlaceholder": "What should the agent do?", + "error": "The job could not be created.", + "cancel": "Cancel", + "starting": "Starting", + "start": "Start job" + }, + "wizard": { + "title": "Add repository", + "intro": "Connect a repository so the dev platform can run isolated jobs against it and open pull requests for your review.", + "backToRepos": "Back to repositories", + "steps": { + "repo": "Repository", + "credentials": "Credentials", + "confirm": "Confirm" + }, + "fields": { + "forge": "Forge", + "owner": "Owner", + "name": "Repository name", + "branch": "Default branch" + }, + "next": "Next", + "back": "Back", + "finish": "Add repository", + "adding": "Adding", + "edit": "Edit", + "summary": { + "github_app": "GitHub App", + "device_flow": "Device flow", + "pat": "Personal access token" + }, + "error": { + "submit": "The repository could not be added. Check the owner, name, and credential, then try again." + }, + "done": { + "heading": "Repository added", + "body": "{repo} is connected. You can start a job from the repository list.", + "toList": "Go to repositories" + }, + "credentials": { + "groupLabel": "Credential mode", + "githubApp": { + "title": "GitHub App — recommended", + "soon": "available soon", + "body": "Per-repository installation, short-lived scoped tokens, commits as omadia-dev[bot]. The token cannot merge — structurally." + }, + "deviceFlow": { + "title": "Device flow — quick start", + "body": "Sign in with your GitHub account through a one-time device code. The fastest way to try the dev platform." + }, + "deviceTradeoffs": { + "heading": "What this mode means", + "asUser": "commits appear as your GitHub user, not a bot", + "repoWide": "the token grants access to all your repositories, not just this one", + "canMerge": "the token can merge — the no-merge rule is enforced by omadia policy only, not by token scope", + "noWebhooks": "webhook triggers stay disabled for this repository" + }, + "device": { + "codeAria": "Device code {code}", + "copyCode": "Copy code", + "copied": "Copied", + "waiting": "Waiting for authorization", + "authorizedAs": "Authorized as {login}", + "expired": "Code expired", + "error": "Authorization failed", + "restart": "Start again" + }, + "pat": { + "title": "Fine-grained PAT / deploy key", + "body": "Paste a fine-grained personal access token scoped to this repository. Also the path for GitLab and Gitea.", + "label": "Token", + "placeholder": "github_pat_…" + } + }, + "checks": { + "label": "branch protection on {branch}", + "enabled": "enabled", + "missing": "missing", + "unknown": "could not verify", + "warning": "without branch protection a device-flow or PAT token could push to {branch} directly. Enable it in the repository settings — the dev platform will only ever push to omadia/job-* branches, but protection makes that structural.", + "unknownHint": "The stored token cannot read the protection settings — classic device-flow tokens usually lack admin read. You can re-check any time." + } + }, + "detail": { + "jobLabel": "job {hash}", + "loading": "Loading", + "notFound": "No such job.", + "railLabel": "Pipeline phases", + "phases": { + "analyze": "analyze", + "bootstrap": "bootstrap", + "plan": "plan", + "clarify": "clarify", + "gate": "gate", + "implement": "implement", + "review": "review", + "pr": "pr" + }, + "phaseSkipped": "skipped — no questions", + "toolCall": { + "pending": "running", + "failed": "failed", + "noOutput": "(no output)", + "prompt": "Prompt", + "result": "Result", + "output": "Output", + "moreDiffLines": { + "one": "… {count} more line", + "other": "… {count} more lines" + } + }, + "openPr": "Open pull request", + "artifactError": "This phase's recorded output could not be loaded.", + "logEmpty": "No log output yet.", + "scrollToBottom": "Scroll to bottom", + "connection": { + "live": "live · last event {seconds}s ago", + "reconnecting": "reconnecting", + "closed": "stream closed — job finished" + }, + "cancel": { + "action": "Cancel", + "title": "Cancel job?", + "body": "The runner is terminated and the branch is kept.", + "confirm": "Cancel job", + "cancelLabel": "Keep running" + }, + "delete": { + "action": "Delete", + "title": "Delete job?", + "body": "The job, its log, and its artifacts are removed permanently. Any branch or PR it created is kept.", + "confirm": "Delete job", + "cancelLabel": "Keep it" + }, + "sidebar": { + "backend": "Backend", + "agent": "Agent", + "branch": "Branch", + "source": "Source", + "createdBy": "Created by", + "tokens": "Tokens in / out", + "cost": "Cost" + } + }, + "repoDetail": { + "back": "Back to repositories", + "loading": "Loading", + "loadError": "This repository could not be loaded.", + "forge": "Forge", + "branch": "Default branch", + "credential": "Credential", + "credentialHeading": "Credential", + "credentialCurrent": "Current mode: {kind}", + "runsTests": "Runs tests", + "yes": "yes", + "no": "no", + "protectionHeading": "Branch protection", + "recheck": "Re-check", + "rechecking": "Checking", + "budget": { + "heading": "Budget", + "help": "Cap the spend per job for this repository. Leave empty to use the platform default.", + "costLabel": "Cost budget (USD per job)", + "costPlaceholder": "e.g. 5.00", + "costError": "Enter a positive amount, or leave empty for the default.", + "save": "Save budget", + "saving": "Saving", + "saved": "Saved", + "saveError": "The budget could not be saved." + }, + "webhook": { + "heading": "Webhook triggers", + "help": "When enabled, applying the trigger label to an issue starts a job — but only for an allow-listed sender. An empty sender list keeps the webhook off.", + "enable": "Enable", + "disable": "Disable", + "saving": "Saving", + "statusLabel": "Status", + "enabledStatus": "Enabled", + "disabledStatus": "Disabled", + "triggerLabelLabel": "Trigger label", + "sendersLabel": "Allowed senders", + "sendersEmpty": "None — webhook triggers are off until a sender is added.", + "saveError": "The webhook setting could not be saved." + } + } + }, + "chat": { + "devJob": { + "heading": "Dev job", + "viewJob": "Open job", + "viewPr": "View PR", + "connectionLost": "connection lost", + "gate": { + "title": "Approval needed", + "approve": "Approve", + "reject": "Reject", + "resolving": "Submitting…", + "error": "Could not resolve the gate." + } + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..c8f6d03 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,41 @@ +{ + "name": "@omadia/dev-platform-ui", + "version": "0.2.0", + "private": true, + "type": "module", + "description": "The Dev Platform operator SPA. Built with Vite into `packages/plugin/ui/`, shipped inside the plugin ZIP and served by core at `/p//ui/` (epic byte5ai/omadia#470, P2 / C8).", + "license": "MIT", + "scripts": { + "build": "vite build && node scripts/check-ui-vocabulary.mjs", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "lint:vocabulary": "node scripts/check-ui-vocabulary.mjs", + "clean": "rm -rf dist ../plugin/ui *.tsbuildinfo" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "jsdom": "^27.0.0", + "typescript": "^6.0.2", + "vite": "^7.1.0", + "vitest": "^3.2.0" + }, + "engines": { + "node": ">=20" + }, + "homepage": "https://github.com/byte5ai/omadia-dev-platform", + "repository": { + "type": "git", + "url": "https://github.com/byte5ai/omadia-dev-platform.git", + "directory": "packages/ui" + } +} diff --git a/packages/ui/scripts/check-ui-vocabulary.d.mts b/packages/ui/scripts/check-ui-vocabulary.d.mts new file mode 100644 index 0000000..540d9d8 --- /dev/null +++ b/packages/ui/scripts/check-ui-vocabulary.d.mts @@ -0,0 +1,36 @@ +/** + * Types for `check-ui-vocabulary.mjs`. + * + * The checker is plain `.mjs` because it must run as a build step with no + * compile in front of it — a gate that needs the build to work before it can + * run is a gate that cannot guard the build. This declaration is what lets + * `test/vocabulary.test.ts` consume it under `strict`, so the offender shape is + * a checked contract in the tests rather than `any`. + * + * `kind` mirrors the union core's ingest scanner reports, plus the two this + * repo adds: `unknown-class` (a well-formed class the served sheet does not + * define — the shape ingest cannot see) and `stylesheet-emitted`. + */ +export interface VocabularyOffender { + /** Package-relative path. */ + readonly file: string; + /** 1-based line number inside that file. */ + readonly line: number; + /** The matched token, truncated. */ + readonly token: string; + readonly kind: + | 'arbitrary-value' + | 'arbitrary-variant' + | 'unknown-class' + | 'stylesheet-emitted'; +} + +export interface CheckOptions { + /** Directory holding the built bundle (`../plugin/ui`). */ + bundleDir: string; + /** Source tree for the exact-precision second pass. Omit to skip it. */ + sourceDir?: string | undefined; +} + +/** Returns every violation found. An empty array is the only clean result. */ +export function check(options: CheckOptions): VocabularyOffender[]; diff --git a/packages/ui/scripts/check-ui-vocabulary.mjs b/packages/ui/scripts/check-ui-vocabulary.mjs new file mode 100644 index 0000000..dbf1ffe --- /dev/null +++ b/packages/ui/scripts/check-ui-vocabulary.mjs @@ -0,0 +1,342 @@ +#!/usr/bin/env node +/** + * check-ui-vocabulary.mjs — fail the build on any class core does not serve. + * + * node scripts/check-ui-vocabulary.mjs # checks ../plugin/ui + src + * node scripts/check-ui-vocabulary.mjs --bundle X --source Y + * + * ## The failure this prevents + * + * A plugin ships no stylesheet. It links the one core generates from a + * finite, pre-declared vocabulary, because Tailwind emits only classes it saw + * at build time and a plugin installed at runtime from another repository is + * never seen. A class outside that vocabulary therefore does not error + * anywhere — it renders **unstyled**, on the operator's screen, and nowhere + * else. Silent and remote is the worst pair of properties a defect can have, + * so this runs at build time in the repo that produced the class. + * + * ## Three checks, and why it is three rather than one + * + * **1. No stylesheet in the output.** `.css` is absent from the plugin-ZIP + * extension allowlist AND from the static router's Content-Type table. A + * bundle that emitted one would be rejected at ingest, or — if it slipped + * past — would ship with a `` that 404s. Cheapest possible check, + * catches an accidental `import './x.css'` the moment it lands. + * + * **2. Arbitrary values, scanned in the built JS.** This runs the SAME two + * regexes core runs at package ingest + * (`middleware/src/plugins/tailwindArbitraryValueScan.ts`), over the same + * file scope (`ui/**\/*.js`, `*.mjs`). It is deliberately a copy rather than + * an import: this repo does not depend on core's middleware, and a check that + * is only *approximately* the ingest check would let a package build green + * here and be rejected there — which is a worse experience than failing here. + * Parity is asserted by `test/vocabulary.test.ts` against the documented + * patterns. + * + * **3. Whitelist diff.** Ingest does NOT catch `bg-blue-500`. It is not an + * arbitrary value, it is an ordinary-looking class that simply does not exist + * in the served sheet, and only a whitelist can see that. So this is the + * check that has no counterpart in core, and it is the one that actually + * protects the rendered page. + * + * ## Where the whitelist diff looks, and why in two places + * + * The bundle scan (2) is exact. The whitelist diff (3) is not, and cannot be: + * a minified bundle is a soup of strings and only some of them are class + * lists. Prose, i18n keys and CSS-in-JS all look similar enough that a naive + * token diff would drown in false positives. + * + * So the diff runs twice, and the two passes have opposite error profiles: + * + * | Pass | Precision | What it misses | + * |---|---|---| + * | `src/**` — every `className` attribute and `cx(...)` argument, parsed from the real source | exact — an attribute is unambiguously a class list | classes assembled at runtime | + * | `ui/**\/*.js` — literals that look like class lists | heuristic (see `looksLikeClassList`) | a literal whose tokens are ALL unknown | + * + * The source pass catches the standalone `"bg-blue-500"` the bundle + * heuristic skips; the bundle pass catches a class that reached the output + * from a dependency the source pass never reads. Neither alone is enough. + * Both are cheap. + * + * ## The limit, stated rather than papered over + * + * A class assembled at runtime (`` `bg-${tone}` ``) defeats every static + * check here, exactly as it defeats core's. Nothing claims otherwise. The + * vocabulary is the contract; this is its cheap enforcement, and code that + * routes around it merely ends up unstyled. The codebase's answer is to write + * the branches out in full (`tone === 'danger' ? 'bg-danger' : 'bg-success'`) + * so both literals are visible to this scanner — see `src/lib/cx.ts`. + */ +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { dirname, extname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +// ── the two ingest patterns, copied verbatim from core ────────────────────── +// Any edit here must be mirrored in `tailwindArbitraryValueScan.ts` and vice +// versa; `test/vocabulary.test.ts` pins the behaviour of both. + +/** `w-[137px]`, `md:hover:bg-[#abc]`, `data-[state=open]:!w-[137px]`. */ +const ARBITRARY_VALUE = + /(?tr]:border`, `[&_p]:mt-2` — arbitrary variants. */ +const ARBITRARY_VARIANT = /(\[&[^\]\s"'`]*\](?::[a-z0-9[\]&_>-]+)?)/g; + +/** Core caps its ingest scan at 200 files / 8 MB. Mirrored so a bundle that + * would be too large to scan there does not build green here. */ +const MAX_FILES = 200; +const MAX_BYTES = 8 * 1024 * 1024; + +/** + * Non-Tailwind classes this SPA is allowed to use. Each is either a hook the + * bundle's own code targets or a class the served sheet defines outside the + * utility vocabulary. Anything added here is a class that will NOT be styled + * by core, so it must be styled by nothing at all — a pure behaviour hook. + */ +const NON_TAILWIND_ALLOWED = new Set([ + // Targeted by `useStickToBottom`'s scroll math, never styled. + 'js-log-viewport', +]); + +function readVocabulary() { + const raw = readFileSync(join(pkgRoot, 'vocabulary', 'classes.txt'), 'utf8'); + const set = new Set( + raw + .split('\n') + .map((l) => l.trim()) + .filter((l) => l !== '' && !l.startsWith('#')), + ); + if (set.size < 100) { + throw new Error( + `vocabulary/classes.txt holds only ${set.size} entries — regenerate it with scripts/extract-vocabulary.mjs`, + ); + } + return set; +} + +function walk(dir, exts, out = []) { + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return out; + } + for (const e of entries) { + const full = join(dir, e.name); + if (e.isDirectory()) walk(full, exts, out); + else if (exts.has(extname(e.name))) out.push(full); + } + return out; +} + +/** 1-based line number of `index` within `content`. */ +function lineOf(content, index) { + let line = 1; + for (let i = 0; i < index && i < content.length; i++) { + if (content[i] === '\n') line += 1; + } + return line; +} + +// ── check 1: no stylesheet ───────────────────────────────────────────────── + +function checkNoCss(bundleDir) { + const css = walk(bundleDir, new Set(['.css', '.scss', '.sass', '.less'])); + return css.map((f) => ({ + file: relative(pkgRoot, f), + line: 1, + token: extname(f), + kind: 'stylesheet-emitted', + })); +} + +// ── check 2: arbitrary values, core's patterns ───────────────────────────── + +function scanArbitrary(files) { + const offenders = []; + let budget = MAX_BYTES; + for (const file of files.slice(0, MAX_FILES)) { + const size = statSync(file).size; + if (size > budget) break; + budget -= size; + const content = readFileSync(file, 'utf8'); + for (const [re, kind] of [ + [ARBITRARY_VALUE, 'arbitrary-value'], + [ARBITRARY_VARIANT, 'arbitrary-variant'], + ]) { + re.lastIndex = 0; + let m; + while ((m = re.exec(content)) !== null) { + offenders.push({ + file: relative(pkgRoot, file), + line: lineOf(content, m.index), + token: m[1].slice(0, 120), + kind, + }); + } + } + } + return offenders; +} + +// ── check 3: whitelist diff ──────────────────────────────────────────────── + +/** A single token that could plausibly be a Tailwind utility. */ +const CLASS_TOKEN = /^-?[a-z0-9][a-z0-9:./_-]*$/; + +/** + * Is this string literal a class list rather than prose, a key or a path? + * + * The test is "every token is class-shaped AND at least one token is a class + * we know". The second half is what keeps the false-positive rate usable: an + * i18n key like `jobs.table.empty` is class-shaped but contains no known + * class, so it drops out. The cost is a false negative on a literal whose + * tokens are ALL unknown — which the `src/**` pass covers. + */ +function looksLikeClassList(value, vocabulary) { + if (value.length === 0 || value.length > 500) return false; + const tokens = value.split(/\s+/).filter(Boolean); + if (tokens.length === 0) return false; + if (!tokens.every((t) => CLASS_TOKEN.test(t))) return false; + return tokens.some((t) => vocabulary.has(t)); +} + +const STRING_LITERAL = /"([^"\\\n]*(?:\\.[^"\\\n]*)*)"|'([^'\\\n]*(?:\\.[^'\\\n]*)*)'|`([^`\\$]*)`/g; + +function diffBundle(files, vocabulary) { + const offenders = []; + for (const file of files.slice(0, MAX_FILES)) { + const content = readFileSync(file, 'utf8'); + STRING_LITERAL.lastIndex = 0; + let m; + while ((m = STRING_LITERAL.exec(content)) !== null) { + const value = m[1] ?? m[2] ?? m[3]; + if (value === undefined) continue; + if (!looksLikeClassList(value, vocabulary)) continue; + for (const token of value.split(/\s+/).filter(Boolean)) { + if (vocabulary.has(token) || NON_TAILWIND_ALLOWED.has(token)) continue; + offenders.push({ + file: relative(pkgRoot, file), + line: lineOf(content, m.index), + token, + kind: 'unknown-class', + }); + } + } + } + return offenders; +} + +/** + * `className="..."`, `className={'...'}` and every string argument to `cx(`. + * Parsed from source, where an attribute is unambiguously a class list, so + * this pass needs no "at least one known token" escape hatch. + */ +const SOURCE_CLASS_SITES = [ + /className\s*=\s*"([^"]*)"/g, + /className\s*=\s*\{?\s*'([^']*)'/g, + /\bcx\(([^)]*)\)/g, +]; + +function diffSource(files, vocabulary) { + const offenders = []; + for (const file of files) { + const content = readFileSync(file, 'utf8'); + for (const re of SOURCE_CLASS_SITES) { + re.lastIndex = 0; + let m; + while ((m = re.exec(content)) !== null) { + // For `cx(...)` only the string literals inside are class lists; the + // conditionals between them are code. + const literals = + re === SOURCE_CLASS_SITES[2] + ? [...m[1].matchAll(/'([^']*)'|"([^"]*)"/g)].map((x) => x[1] ?? x[2] ?? '') + : [m[1] ?? '']; + for (const literal of literals) { + for (const token of literal.split(/\s+/).filter(Boolean)) { + if (token.includes('${')) continue; // runtime-assembled; see header + if (vocabulary.has(token) || NON_TAILWIND_ALLOWED.has(token)) continue; + offenders.push({ + file: relative(pkgRoot, file), + line: lineOf(content, m.index), + token, + kind: 'unknown-class', + }); + } + } + } + } + } + return offenders; +} + +// ── entry point ──────────────────────────────────────────────────────────── + +export function check({ bundleDir, sourceDir }) { + const vocabulary = readVocabulary(); + const bundleFiles = walk(bundleDir, new Set(['.js', '.mjs'])).sort(); + const sourceFiles = sourceDir + ? walk(sourceDir, new Set(['.ts', '.tsx'])).sort() + : []; + + return [ + ...checkNoCss(bundleDir), + ...scanArbitrary(bundleFiles), + ...diffBundle(bundleFiles, vocabulary), + ...diffSource(sourceFiles, vocabulary), + ]; +} + +function argValue(flag, fallback) { + const i = process.argv.indexOf(flag); + return i === -1 ? fallback : process.argv[i + 1]; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const bundleDir = resolve(pkgRoot, argValue('--bundle', '../plugin/ui')); + const sourceArg = argValue('--source', 'src'); + const sourceDir = sourceArg === 'none' ? undefined : resolve(pkgRoot, sourceArg); + + const bundleFileCount = walk(bundleDir, new Set(['.js', '.mjs'])).length; + if (bundleFileCount === 0) { + // A vocabulary check that found no bundle to check is not a pass. This is + // the shape of green that hides a build that never ran. + console.error( + `✗ no .js found under ${bundleDir} — run \`vite build\` before the vocabulary check`, + ); + process.exit(1); + } + + const offenders = check({ bundleDir, sourceDir }); + if (offenders.length === 0) { + console.log( + `✓ UI vocabulary clean — ${bundleFileCount} bundle file(s), no stylesheet, no arbitrary values, no unknown classes`, + ); + process.exit(0); + } + + // Deduplicate: the same class in the same file on the same line, found by + // two passes, is one problem. + const seen = new Set(); + const unique = offenders.filter((o) => { + const key = `${o.file}:${o.line}:${o.token}:${o.kind}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + + console.error(`✗ ${unique.length} UI vocabulary violation(s):\n`); + for (const o of unique.slice(0, 100)) { + console.error(` ${o.file}:${o.line} ${o.kind} ${o.token}`); + } + if (unique.length > 100) console.error(` … and ${unique.length - 100} more`); + console.error( + '\nThe served sheet contains only the classes in vocabulary/classes.txt.\n' + + 'A class outside it renders UNSTYLED at runtime, silently. Either use a\n' + + 'class from the vocabulary, or widen the vocabulary in core first — see\n' + + 'vocabulary/README.md.', + ); + process.exit(1); +} diff --git a/packages/ui/scripts/extract-vocabulary.d.mts b/packages/ui/scripts/extract-vocabulary.d.mts new file mode 100644 index 0000000..8f647ff --- /dev/null +++ b/packages/ui/scripts/extract-vocabulary.d.mts @@ -0,0 +1,7 @@ +/** Types for `extract-vocabulary.mjs` — see `vocabulary/README.md`. */ + +/** + * Pull every class selector out of a stylesheet, unescaped into the spelling a + * `class` attribute uses (`hover:bg-accent`, not `hover\:bg-accent`). + */ +export function extractClasses(css: string): string[]; diff --git a/packages/ui/scripts/extract-vocabulary.mjs b/packages/ui/scripts/extract-vocabulary.mjs new file mode 100644 index 0000000..787ee86 --- /dev/null +++ b/packages/ui/scripts/extract-vocabulary.mjs @@ -0,0 +1,86 @@ +#!/usr/bin/env node +/** + * extract-vocabulary.mjs — regenerate `vocabulary/classes.txt` from core's + * generated plugin stylesheet. + * + * node scripts/extract-vocabulary.mjs \ + * ../odoo-bot/middleware/assets/plugin-ui/plugin-ui.css \ + * vocabulary/classes.txt + * + * The vocabulary is read out of the COMPILED stylesheet rather than expanded + * from the `@source inline("{p,px,py}-{0..12}")` declarations that produced + * it. A brace expander written here would be a second implementation of + * Tailwind's, and the two would drift silently in the permissive direction — + * the failure mode this whole gate exists to prevent. The selectors in the + * artifact are what a browser will actually match, so they are the answer. + * + * Escapes are unescaped on the way out: Tailwind writes `.hover\:bg-accent` + * and `.max-w-2xl`, and the class ATTRIBUTE that matches them contains + * `hover:bg-accent`. Comparing escaped selectors against attribute text would + * reject every variant class in the bundle. + */ +import { readFileSync, writeFileSync } from 'node:fs'; + +/** + * Pull every class selector out of a stylesheet. + * + * Naively globbing `/\.([\w-]+)/` over the whole file also matches the `.25` + * in `padding: 0.25rem` and the `.5` in `margin: .5em`, which would seed the + * whitelist with junk tokens like `25rem`. So the file is walked and only the + * text that PRECEDES an opening brace — the selector — is examined. Anything + * after a `;` or inside a declaration block is discarded. + * + * @param {string} css + * @returns {string[]} sorted, unescaped class names + */ +export function extractClasses(css) { + const stripped = css.replace(/\/\*[\s\S]*?\*\//g, ''); + + /** @type {string[]} */ + const selectors = []; + let buf = ''; + let depth = 0; + for (const ch of stripped) { + if (ch === '{') { + selectors.push(buf); + depth += 1; + buf = ''; + } else if (ch === '}') { + depth -= 1; + buf = ''; + } else if (ch === ';') { + buf = ''; + } else { + buf += ch; + } + } + + const set = new Set(); + // A class starts the selector or follows a combinator/comma/paren — never a + // digit, which is what keeps decimal values out. + const CLASS = /(?:^|[\s,>+~()])\.((?:[A-Za-z0-9_-]|\\.)+)/g; + for (const selector of selectors) { + if (selector.trimStart().startsWith('@')) continue; + CLASS.lastIndex = 0; + let m; + while ((m = CLASS.exec(` ${selector}`)) !== null) { + set.add(m[1].replace(/\\(.)/g, '$1')); + } + } + return [...set].sort(); +} + +const [, , input, output] = process.argv; +if (input && output) { + const classes = extractClasses(readFileSync(input, 'utf8')); + if (classes.length < 100) { + // A parse that quietly produced almost nothing would write an empty + // whitelist, and an empty whitelist rejects the entire bundle rather than + // accepting it — loud, but for the wrong reason. Say what happened. + throw new Error( + `only ${classes.length} classes extracted from ${input} — that is not a plugin-ui stylesheet`, + ); + } + writeFileSync(output, `${classes.join('\n')}\n`); + console.log(`wrote ${classes.length} classes to ${output}`); +} diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx new file mode 100644 index 0000000..6fa89e0 --- /dev/null +++ b/packages/ui/src/App.tsx @@ -0,0 +1,67 @@ +import type { ReactElement } from 'react'; + +import { I18nProvider } from '@/lib/i18n'; +import { RouterProvider, matchRoute, useRouter } from '@/lib/router'; +import type { Locale } from '@/lib/appearance'; +import { HubScreen } from '@/screens/HubScreen'; +import { JobDetailScreen } from '@/screens/JobDetailScreen'; +import { RepoDetailScreen } from '@/screens/RepoDetailScreen'; +import { RepoNewScreen } from '@/screens/RepoNewScreen'; + +/** + * The SPA shell: providers plus the four-way route switch. + * + * There is no navigation chrome here on purpose. This document is embedded in + * an iframe by web-ui's `/plugin-ui/` page, which already renders + * the shell's header, sidebar and page title around it. Drawing a second + * header inside the frame would give the operator two of everything. + * + * The four routes are the four screens the epic's acceptance matrix names + * (`acceptance.md` §2.7): hub, job detail, repo detail, add-repo wizard. + */ +function Routes(): ReactElement { + const { path } = useRouter(); + const route = matchRoute(path); + + switch (route.kind) { + case 'hub': + return ; + case 'job': + return ; + case 'repo-new': + return ; + case 'repo': + return ; + case 'not-found': + return ; + } +} + +function NotFound({ path }: { path: string }): ReactElement { + // No i18n key exists for this: core's Next router answered an unknown + // dev-platform path with the shell's own 404 page, so the string was never + // in `adminDevPlatform.*`. Inventing a key here would put a message in the + // catalogue that core's translators never see. The path is the useful part. + return ( +
+

+ Unknown route: {path} +

+

+ + ← Dev Platform + +

+
+ ); +} + +export function App({ locale }: { locale: Locale }): ReactElement { + return ( + + + + + + ); +} diff --git a/packages/ui/src/components/AddRepoWizard.tsx b/packages/ui/src/components/AddRepoWizard.tsx new file mode 100644 index 0000000..dc6d701 --- /dev/null +++ b/packages/ui/src/components/AddRepoWizard.tsx @@ -0,0 +1,232 @@ +import { useCallback, useState } from 'react'; + +import { Link } from '@/lib/router'; +import { useTranslations } from '@/lib/i18n'; + +import { Button } from '@/components/ui/Button'; +import { CredentialStep, type CredentialChoice } from '@/components/CredentialStep'; +import { ProtectionCheckList } from '@/components/ProtectionCheckList'; +import { createRepo, type DevRepoView } from '@/lib/api'; + +/** + * Epic #470 W0 — the add-repo wizard (UI spec §3). Own page, not a modal — + * device flow leaves for github.com. A vertical step sequence: completed steps + * collapse to a one-line summary with a ghost "Edit"; the active step is + * expanded. No spinner — the "Add repository" button uses `Button busy`. + * + * Spec drift (recorded): the spec's independent step-3 "Checks" assumes a + * pre-creation branch-protection probe. The W0 backend on this branch folds + * access validation AND the branch-protection check into `POST /repos` (it + * needs the stored credential to probe), and exposes no pre-creation check + * endpoint. So the wizard runs the check AS PART OF add and renders the verdict + * immediately after — which still satisfies "the check runs in the wizard, + * warns loudly, does not block". + */ + +type Step = 'repo' | 'credentials' | 'confirm'; +const ORDER: readonly Step[] = ['repo', 'credentials', 'confirm']; + +const inputCls = + 'rounded-md border-t border-r border-b border-l border-border bg-transparent px-3 py-2 text-sm focus-visible:outline-none focus:border-accent'; + +export function AddRepoWizard(): React.ReactElement { + const t = useTranslations('adminDevPlatform.wizard'); + const [step, setStep] = useState('repo'); + const [owner, setOwner] = useState(''); + const [name, setName] = useState(''); + const [defaultBranch, setDefaultBranch] = useState('main'); + const [credential, setCredential] = useState(null); + const [submitting, setSubmitting] = useState(false); + const [errorKey, setErrorKey] = useState(null); + const [created, setCreated] = useState(null); + + const repoReady = owner.trim().length > 0 && name.trim().length > 0; + const credentialReady = + credential?.kind === 'pat' + ? credential.token.trim().length > 0 + : credential?.kind === 'device_flow' + ? credential.authorized + : false; + + const go = useCallback((next: Step) => { + setStep(next); + setErrorKey(null); + }, []); + + const submit = useCallback(() => { + if (!credential || (credential.kind !== 'device_flow' && credential.kind !== 'pat')) return; + setSubmitting(true); + setErrorKey(null); + void (async () => { + try { + const body = + credential.kind === 'pat' + ? { owner: owner.trim(), name: name.trim(), credential: { kind: 'pat' as const, token: credential.token } } + : { owner: owner.trim(), name: name.trim(), credential: { kind: 'device_flow' as const } }; + const repo = await createRepo(body); + setCreated(repo); + } catch { + setErrorKey('submit'); + } finally { + setSubmitting(false); + } + })(); + }, [credential, owner, name]); + + // Success view — repo created, protection verdict shown (does not block). + if (created) { + return ( +
+

{t('done.heading')}

+

+ {t('done.body', { repo: `${created.owner}/${created.name}` })} +

+
+ +
+
+ + + +
+
+ ); + } + + return ( +
+ {/* Step 1 — Repository */} + go('repo')} + > +
+ + + + +
+ +
+
+
+ + {/* Step 2 — Credentials */} + ORDER.indexOf('credentials')} + summary={credentialReady && credential ? t(`summary.${credential.kind}`) : ''} + onEdit={() => go('credentials')} + > + +
+ + +
+
+ + {/* Step 3 — Confirm */} + go('confirm')}> +
+
{t('fields.owner')}
+
{owner}
+
{t('fields.name')}
+
{name}
+
{t('fields.branch')}
+
{defaultBranch}
+
{t('steps.credentials')}
+
{credential ? t(`summary.${credential.kind}`) : ''}
+
+ {credential?.kind === 'device_flow' ? ( +
+ {t('credentials.deviceTradeoffs.canMerge')} +
+ ) : null} + {errorKey ?

{t('error.submit')}

: null} +
+ + +
+
+
+ ); +} + +function StepShell({ + index, + title, + active, + done, + summary, + onEdit, + children, +}: { + index: number; + title: string; + active: boolean; + done: boolean; + summary: string; + onEdit: () => void; + children: React.ReactNode; +}): React.ReactElement { + const t = useTranslations('adminDevPlatform.wizard'); + if (!active && done) { + return ( +
+ + + {index}. {title} + {summary ? {summary} : null} + + +
+ ); + } + return ( +
+

+ {index}. {title} +

+ {active ? children : null} +
+ ); +} diff --git a/packages/ui/src/components/BindGithubAppPanel.tsx b/packages/ui/src/components/BindGithubAppPanel.tsx new file mode 100644 index 0000000..4452123 --- /dev/null +++ b/packages/ui/src/components/BindGithubAppPanel.tsx @@ -0,0 +1,154 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { useTranslations } from '@/lib/i18n'; + +import { Button } from '@/components/ui/Button'; +import { + bindGithubAppCredential, + devPlatformErrorCode, + listGithubApps, + type DevGithubAppSummary, +} from '@/lib/api'; + +/** + * Epic #470 W2 — bind an existing repo to a `github_app` credential (UI spec §2, + * "repo credential step"). This is the W2 upgrade path for a repo onboarded in + * W0 with a device-flow or PAT credential: pick the GitHub App path and supply + * the installation that covers this repo. The middleware proves the installation + * actually covers the repo before it persists anything (a wrong id is a 400, + * never a silent bind), and returns branch-protection `warnings` we surface. + * + * Why an installation id INPUT and not a picker: the browser API exposes the + * App registry and each App's installation COUNT, but not the installation ids + * (those are minted by GitHub during install and returned to the post-install + * `setup` redirect). So the operator installs the App, then pastes the id GitHub + * showed — the App list below links straight to each App to install/inspect. + * + * No spinner (Lume §7.3): the bind button carries `busy`. + */ + +const inputCls = + 'rounded-md border-t border-r border-b border-l border-border bg-transparent px-3 py-2 text-sm focus-visible:outline-none focus:border-accent'; + +const ERROR_CODE_KEYS: Record = { + 'devplatform.installation_not_covering': 'notCovering', + 'devplatform.unknown_installation': 'unknownInstallation', + 'devplatform.invalid_installation': 'invalidInstallation', + 'devplatform.app_unusable': 'appUnusable', +}; + +export function BindGithubAppPanel({ + repoId, + onBound, +}: { + repoId: string; + onBound: () => void; +}): React.ReactElement { + const t = useTranslations('adminDevPlatform.bindApp'); + const [apps, setApps] = useState(null); + const [installationId, setInstallationId] = useState(''); + const [busy, setBusy] = useState(false); + const [errorKey, setErrorKey] = useState(null); + const [warnings, setWarnings] = useState(null); + + useEffect(() => { + let alive = true; + void listGithubApps().then( + (res) => { + if (alive) setApps(res.apps); + }, + () => { + if (alive) setApps([]); + }, + ); + return () => { + alive = false; + }; + }, []); + + const bind = useCallback(() => { + setBusy(true); + setErrorKey(null); + setWarnings(null); + void (async () => { + try { + const res = await bindGithubAppCredential(repoId, installationId); + setWarnings(res.warnings); + onBound(); + } catch (err) { + const code = devPlatformErrorCode(err); + setErrorKey((code && ERROR_CODE_KEYS[code]) ?? 'generic'); + } finally { + setBusy(false); + } + })(); + }, [installationId, onBound, repoId]); + + const ready = installationId.trim().length > 0; + + return ( +
+

{t('intro')}

+ + {apps === null ? ( +

{t('loadingApps')}

+ ) : apps.length === 0 ? ( +

{t('noApps')}

+ ) : ( + + )} + + + + {warnings !== null ? ( + warnings.length > 0 ? ( +
+

+ {t('boundWithWarnings')} +

+
    + {warnings.map((w) => ( +
  • {w}
  • + ))} +
+
+ ) : ( +

{t('bound')}

+ ) + ) : null} + + {errorKey ?

{t(`errors.${errorKey}`)}

: null} + +
+ +
+
+ ); +} diff --git a/packages/ui/src/components/ConfirmDialog.tsx b/packages/ui/src/components/ConfirmDialog.tsx new file mode 100644 index 0000000..86292e4 --- /dev/null +++ b/packages/ui/src/components/ConfirmDialog.tsx @@ -0,0 +1,96 @@ +import { useEffect, useRef, type ReactElement } from 'react'; + +import { Button } from '@/components/ui/Button'; +import { BORDER, cx } from '@/lib/cx'; + +/** + * Minimal modal-confirm, ported from `web-ui/app/_components/ConfirmDialog.tsx`. + * + * Behaviour is unchanged — focus opens on Cancel (deliberate friction before a + * destructive action, so Enter cancels and confirming needs an explicit Tab or + * click), Escape cancels, a backdrop click cancels. + * + * Only the classes changed. Core paints the backdrop with + * `bg-[color:var(--bg-modal-overlay)]`, an arbitrary value; the served + * vocabulary has no overlay token, so `bg-bg-soft` stands in. It is opaque + * rather than translucent — the dialog still reads as modal because it is + * `fixed inset-0` above everything, but the content behind it is hidden rather + * than dimmed. Widening the vocabulary with an overlay token is the real fix + * and is listed in the P2 report. + */ +export interface ConfirmDialogProps { + open: boolean; + title: string; + body?: string; + confirmLabel: string; + cancelLabel: string; + /** `danger` paints the confirm button red. */ + tone?: 'neutral' | 'danger'; + onConfirm: () => void; + onCancel: () => void; +} + +export function ConfirmDialog({ + open, + title, + body, + confirmLabel, + cancelLabel, + tone = 'neutral', + onConfirm, + onCancel, +}: ConfirmDialogProps): ReactElement | null { + const cancelRef = useRef(null); + + useEffect(() => { + if (!open) return; + cancelRef.current?.focus(); + }, [open]); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent): void => { + if (e.key === 'Escape') { + e.preventDefault(); + onCancel(); + } + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [open, onCancel]); + + if (!open) return null; + + return ( +
{ + if (e.target === e.currentTarget) onCancel(); + }} + > +
+

+ {title} +

+ {body &&

{body}

} +
+ + +
+
+
+ ); +} diff --git a/packages/ui/src/components/CredentialStep.tsx b/packages/ui/src/components/CredentialStep.tsx new file mode 100644 index 0000000..d0b2ece --- /dev/null +++ b/packages/ui/src/components/CredentialStep.tsx @@ -0,0 +1,140 @@ +import { useCallback, useState } from 'react'; + +import { useTranslations } from '@/lib/i18n'; + +import { DeviceFlowPanel } from '@/components/DeviceFlowPanel'; + +/** + * Epic #470 W0 — the credentials step of the add-repo wizard (UI spec §3). + * Three radio-cards; the selected one gets an accent edge + `.` + * (the spec-sanctioned selection recipe — edge/glow, no state fill). GitHub App + * is W2, disabled here. The device-flow card renders the honest trade-off block + * — an `--warning`-left-edge plain-language statement — BEFORE the mode can be + * confirmed. PAT is a manual password paste. + */ + +export type CredentialChoice = + | { kind: 'github_app' } + | { kind: 'device_flow'; authorized: boolean; login: string | null } + | { kind: 'pat'; token: string }; + +type Mode = 'github_app' | 'device_flow' | 'pat'; + +const CARD_BASE = 'block cursor-pointer rounded-lg border-t border-r border-b border-l p-4 text-left'; + +export function CredentialStep({ + onChange, +}: { + onChange: (choice: CredentialChoice) => void; +}): React.ReactElement { + const t = useTranslations('adminDevPlatform.wizard.credentials'); + const [mode, setMode] = useState(null); + const [patToken, setPatToken] = useState(''); + + const select = useCallback( + (next: Mode) => { + setMode(next); + if (next === 'github_app') onChange({ kind: 'github_app' }); + if (next === 'device_flow') onChange({ kind: 'device_flow', authorized: false, login: null }); + if (next === 'pat') onChange({ kind: 'pat', token: patToken }); + }, + [onChange, patToken], + ); + + const cardClass = (m: Mode, disabled = false): string => { + const selected = mode === m; + return `${CARD_BASE} ${ + selected + ? 'border-accent' + : 'border-border hover:border-border-strong' + } ${disabled ? 'cursor-not-allowed opacity-60' : ''}`; + }; + + return ( +
+ {/* GitHub App — recommended (W2, disabled here) */} +
+
+ {t('githubApp.title')} + {t('githubApp.soon')} +
+

{t('githubApp.body')}

+
+ + {/* Device flow — quick start */} +
select('device_flow')} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + select('device_flow'); + } + }} + className={cardClass('device_flow')} + > + {t('deviceFlow.title')} +

{t('deviceFlow.body')}

+ {mode === 'device_flow' ? ( + <> +
+

+ {t('deviceTradeoffs.heading')} +

+
    +
  • {t('deviceTradeoffs.asUser')}
  • +
  • {t('deviceTradeoffs.repoWide')}
  • +
  • {t('deviceTradeoffs.canMerge')}
  • +
  • {t('deviceTradeoffs.noWebhooks')}
  • +
+
+ onChange({ kind: 'device_flow', authorized: true, login })} + /> + + ) : null} +
+ + {/* Fine-grained PAT / deploy key */} +
select('pat')} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + select('pat'); + } + }} + className={cardClass('pat')} + > + {t('pat.title')} +

{t('pat.body')}

+ {mode === 'pat' ? ( + + ) : null} +
+
+ ); +} diff --git a/packages/ui/src/components/DeviceFlowPanel.tsx b/packages/ui/src/components/DeviceFlowPanel.tsx new file mode 100644 index 0000000..00a36ae --- /dev/null +++ b/packages/ui/src/components/DeviceFlowPanel.tsx @@ -0,0 +1,155 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { useTranslations } from '@/lib/i18n'; + +import { Button } from '@/components/ui/Button'; +import { deviceConnectPoll, deviceConnectStart } from '@/lib/api'; + +/** + * Epic #470 W0 — GitHub device-flow run state (UI spec §3). No spinner anywhere: + * the user code is the focal object and the polling state is carried by the + * status line's TEXT (plus `.`), announced through an + * `aria-live="polite"` region (§13). Poll ticks change text only. On success we + * hand the login back to the wizard, which stages the token server-side. + */ + +type Phase = 'starting' | 'waiting' | 'authorized' | 'expired' | 'error'; + +export function DeviceFlowPanel({ + onAuthorized, +}: { + onAuthorized: (login: string | null) => void; +}): React.ReactElement { + const t = useTranslations('adminDevPlatform.wizard.credentials.device'); + const [phase, setPhase] = useState('starting'); + const [userCode, setUserCode] = useState(''); + const [verificationUri, setVerificationUri] = useState(''); + const [login, setLogin] = useState(null); + const [copied, setCopied] = useState(false); + const intervalRef = useRef(5); + const pollTimer = useRef | null>(null); + // The poll self-schedules through a ref so the callback never has to + // reference itself before its own declaration (react-hooks/immutability). + const pollRef = useRef<() => void>(() => {}); + + const stopPolling = useCallback(() => { + if (pollTimer.current) { + clearTimeout(pollTimer.current); + pollTimer.current = null; + } + }, []); + + const schedulePoll = useCallback(() => { + pollTimer.current = setTimeout(() => pollRef.current(), intervalRef.current * 1000); + }, []); + + const poll = useCallback(() => { + void (async () => { + try { + const res = await deviceConnectPoll(); + if (res.status === 'authorized') { + stopPolling(); + setLogin(res.login ?? null); + setPhase('authorized'); + onAuthorized(res.login ?? null); + return; + } + if (res.status === 'expired') { + stopPolling(); + setPhase('expired'); + return; + } + if (res.status === 'denied' || res.status === 'error') { + stopPolling(); + setPhase('error'); + return; + } + if (typeof res.interval === 'number' && res.interval > 0) intervalRef.current = res.interval; + schedulePoll(); + } catch { + stopPolling(); + setPhase('error'); + } + })(); + }, [onAuthorized, schedulePoll, stopPolling]); + + useEffect(() => { + pollRef.current = poll; + }); + + const start = useCallback(() => { + setPhase('starting'); + setCopied(false); + void (async () => { + try { + const res = await deviceConnectStart(); + setUserCode(res.userCode); + setVerificationUri(res.verificationUri); + intervalRef.current = res.interval > 0 ? res.interval : 5; + setPhase('waiting'); + schedulePoll(); + } catch { + setPhase('error'); + } + })(); + }, [schedulePoll]); + + useEffect(() => { + start(); + return stopPolling; + // eslint-disable-next-line react-hooks/exhaustive-deps -- run once on mount + }, []); + + const copyCode = useCallback(() => { + void navigator.clipboard?.writeText(userCode).then( + () => setCopied(true), + () => setCopied(false), + ); + }, [userCode]); + + return ( +
+
+
+ {userCode || '········'} +
+ +
+ {verificationUri ? ( + + {verificationUri} + + ) : null} +

+ {phase === 'waiting' || phase === 'starting' ? ( + + {t('waiting')} + + + ) : null} + {phase === 'authorized' ? ( + {t('authorizedAs', { login: login ?? '' })} + ) : null} + {phase === 'expired' ? {t('expired')} : null} + {phase === 'error' ? {t('error')} : null} +

+ {phase === 'expired' || phase === 'error' ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/packages/ui/src/components/GateInbox.tsx b/packages/ui/src/components/GateInbox.tsx new file mode 100644 index 0000000..1ab4fba --- /dev/null +++ b/packages/ui/src/components/GateInbox.tsx @@ -0,0 +1,305 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { useFormatter, useTranslations } from '@/lib/i18n'; + +type Formatter = ReturnType; + +import { Button } from '@/components/ui/Button'; +import { ApiError } from '@/lib/apiError'; +import { + DEV_ARTIFACT_PATH, + getArtifactText, + listWaitingGates, + resolveGate, + type DevGateAnswer, + type DevGateView, +} from '@/lib/api'; +import { PrettyArtifact } from '@/components/PrettyArtifact'; + +/** + * Epic #470 W2 — the operator gate inbox (UI spec §5). Lists every job parked at + * `await_human`: its job id, the plan under review (a link to the plan artifact + * plus its sha256), the agent's clarifying questions, the deadline, and the + * holders currently authorized to resolve it. Each gate has an approve/reject + * action — approve carries one answer field per question plus an optional note; + * reject carries the note. + * + * The framing is load-bearing: plan approval here is ADVISORY. The authoritative + * safety control is the diff gate (W3) that reviews the actual patch before the + * PR — this inbox only lets a plan proceed to implementation. The banner says so. + * + * Failure handling (spec §5 authorization): a 403 means the caller is not a + * holder of this gate (a moved role baton re-targeted it) — we say so in place, + * without mutating anything. A 409 means the gate is no longer pending (someone + * else resolved it, or it expired) — we surface it and refresh the list so the + * stale card drops out. No spinner (Lume §7.3): buttons carry `busy`. + */ + +type ListState = + | { kind: 'loading' } + | { kind: 'ready'; gates: DevGateView[] } + | { kind: 'error'; code: 'unauthorized' | 'generic' }; + +export function GateInbox(): React.ReactElement { + const t = useTranslations('adminDevPlatform.gates'); + const [state, setState] = useState({ kind: 'loading' }); + + const load = useCallback(() => { + void listWaitingGates().then( + (res) => setState({ kind: 'ready', gates: res.gates }), + (err) => + setState({ + kind: 'error', + code: err instanceof ApiError && (err.status === 401 || err.status === 403) ? 'unauthorized' : 'generic', + }), + ); + }, []); + + useEffect(load, [load]); + + return ( +
+
+

+ {t('advisoryHeading')} +

+

{t('advisoryBody')}

+
+ + {state.kind === 'loading' ? ( +

{t('loading')}

+ ) : state.kind === 'error' ? ( + state.code === 'unauthorized' ? ( +

{t('unauthorized')}

+ ) : ( +
+ {t('loadError')} + +
+ ) + ) : state.gates.length === 0 ? ( +

{t('empty')}

+ ) : ( + state.gates.map((gate) => ) + )} +
+ ); +} + +type ResolveState = + | { kind: 'idle' } + | { kind: 'notHolder' } + | { kind: 'conflict' } + | { kind: 'error' }; + +type PlanTextState = { kind: 'loading' } | { kind: 'ready'; text: string } | { kind: 'error' } | { kind: 'none' }; + +/** `compact`: drop the deadline/job-id header (the job-detail page already + * shows both) and the outer bordered card — used to embed the gate inline in + * the job's own phase flow instead of only in the standalone gate inbox. */ +export function GateCard({ + gate, + onResolved, + compact = false, +}: { + gate: DevGateView; + onResolved: () => void; + compact?: boolean; +}): React.ReactElement { + const t = useTranslations('adminDevPlatform.gates'); + const format = useFormatter(); + const [answers, setAnswers] = useState>({}); + const [note, setNote] = useState(''); + const [busy, setBusy] = useState<'approve' | 'reject' | null>(null); + const [resolveState, setResolveState] = useState({ kind: 'idle' }); + const [fetchedPlanText, setFetchedPlanText] = useState({ kind: 'loading' }); + // No artifact ⇒ no fetch ever happens — derive 'none' rather than storing it, + // so the effect below never needs a synchronous setState in its early return. + const planText: PlanTextState = gate.planArtifactId ? fetchedPlanText : { kind: 'none' }; + + useEffect(() => { + if (!gate.planArtifactId) return; + let cancelled = false; + setFetchedPlanText({ kind: 'loading' }); + void getArtifactText(gate.planArtifactId).then( + (text) => { + if (!cancelled) setFetchedPlanText({ kind: 'ready', text }); + }, + () => { + if (!cancelled) setFetchedPlanText({ kind: 'error' }); + }, + ); + return () => { + cancelled = true; + }; + }, [gate.planArtifactId]); + + const resolve = useCallback( + (approved: boolean) => { + setBusy(approved ? 'approve' : 'reject'); + setResolveState({ kind: 'idle' }); + void (async () => { + try { + const collected: DevGateAnswer[] = gate.questions + .map((q) => ({ questionId: q.id, text: (answers[q.id] ?? '').trim() })) + .filter((a) => a.text.length > 0); + await resolveGate(gate.id, { + approved, + ...(approved && collected.length > 0 ? { answers: collected } : {}), + ...(note.trim().length > 0 ? { note: note.trim() } : {}), + }); + onResolved(); + } catch (err) { + setBusy(null); + if (err instanceof ApiError && err.status === 403) { + setResolveState({ kind: 'notHolder' }); + return; + } + if (err instanceof ApiError && err.status === 409) { + setResolveState({ kind: 'conflict' }); + // The gate is no longer pending — refresh so this card drops out. + onResolved(); + return; + } + setResolveState({ kind: 'error' }); + } + })(); + }, + [answers, gate.id, gate.questions, note, onResolved], + ); + + return ( +
+
+ {compact ? null : ( +
+ {t('job')} {gate.jobId} +
+ )} +
+ {gate.deadlineAt ? t('deadline', { at: formatTs(gate.deadlineAt, format) }) : t('noDeadline')} +
+
+ +
+
{t('holders')}
+
+ {gate.resolvedHolders.length > 0 ? gate.resolvedHolders.join(', ') : t('noHolders')} +
+
+ +
+
+

+ {t('plan')} +

+
+ {gate.planSha256 ? ( + + {gate.planSha256.slice(0, 12)} + + ) : null} + {gate.planArtifactId ? ( + + {t('viewPlan')} + + ) : null} +
+
+ {planText.kind === 'none' ? ( +

{t('noPlan')}

+ ) : planText.kind === 'loading' ? ( +

{t('planLoading')}

+ ) : planText.kind === 'error' ? ( +

{t('planLoadError')}

+ ) : ( +
+ +
+ )} +
+ + {gate.questions.length > 0 ? ( +
+

+ {t('questions')} +

+ {gate.questions.map((q) => ( +