diff --git a/.github/workflows/audit-freeze.yml b/.github/workflows/audit-freeze.yml new file mode 100644 index 0000000..f20c71b --- /dev/null +++ b/.github/workflows/audit-freeze.yml @@ -0,0 +1,72 @@ +name: Audit Freeze Gate + +# Blocks PRs that touch an audit-frozen path (per audit-prep/ENGAGEMENT.md's +# freeze_paths / freeze_until front matter) unless they carry the +# "audit-approved" label. +# +# SECURITY, two layers: +# +# 1. This job checks out the PR's BASE ref (github.event.pull_request.base.sha), +# not the default `pull_request` merge ref. That means the audit-freeze +# script that actually runs is always the trusted, already-merged version +# -- a PR cannot modify scripts/audit-freeze/*.ts to weaken or disable its +# own gate. +# 2. check.ts itself additionally fetches ENGAGEMENT.md's content from that +# same base SHA via the GitHub API (rather than trusting whatever is on +# disk), and treats ENGAGEMENT.md as an always-frozen path in its own +# right while any freeze is active. See scripts/audit-freeze/check.ts and +# decide.ts for the full rationale. +# +# Net effect: nothing this job does depends on any file content contributed +# by the PR under test. Every input is either the base-ref checkout, or a +# read-only GitHub API call pinned to the base SHA. + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + pull-requests: read + +jobs: + audit-freeze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Base ref, not the PR merge ref -- see security note above. + ref: ${{ github.event.pull_request.base.sha }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Enable corepack (pins pnpm from packageManager field) + run: | + corepack enable + corepack prepare pnpm@10.28.2 --activate + + - name: Check whether audit-freeze exists on the base branch yet + id: gate_exists + run: | + if [ -f scripts/audit-freeze/check.ts ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Install root dependencies (for tsx) + if: steps.gate_exists.outputs.exists == 'true' + run: pnpm install --frozen-lockfile + + - name: Run audit-freeze check + if: steps.gate_exists.outputs.exists == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: npx tsx scripts/audit-freeze/check.ts + + - name: No audit-freeze system on base branch yet + if: steps.gate_exists.outputs.exists == 'false' + run: echo "scripts/audit-freeze/check.ts does not exist on the base branch yet -- nothing to enforce. Expected for the PR that first introduces the audit-freeze system." diff --git a/audit-prep/ENGAGEMENT.md b/audit-prep/ENGAGEMENT.md new file mode 100644 index 0000000..b548e3a --- /dev/null +++ b/audit-prep/ENGAGEMENT.md @@ -0,0 +1,84 @@ +--- +freeze_paths: + - 'stellar/stealth-announcer/**' + - 'stellar/stealth-registry/**' + - 'stellar/stealth-sender/**' + - 'stellar/wraith-names/**' +freeze_until: 'TBD' +--- + +# Audit Engagement + +> **Template.** This document has the front-matter shape and section +> structure `audit-freeze.yml` expects, with the actual engagement details +> left as clearly-marked `[TBD]` placeholders below. The freeze is inactive +> (`freeze_until: "TBD"`) until those placeholders -- and the front matter +> above -- are filled in with real values from a signed SOW. +> +> `freeze_paths` above lists the four core in-scope crates from +> [`audit-prep/README.md`](./README.md) as a starting point; adjust it if +> the signed SOW's scope differs. See [How the freeze works](#how-the-freeze-works) +> below for exactly how these two fields are interpreted. + +## Engagement Summary + +| Field | Value | +| --------------------- | ----- | +| **Audit Firm** | [TBD] | +| **SOW Signed** | [TBD] | +| **Kickoff Date** | [TBD] | +| **Expected Delivery** | [TBD] | + +## Scope + +### In-Scope Crates + +- [TBD] -- e.g. `stellar/stealth-announcer` +- [TBD] -- e.g. `stellar/stealth-registry` +- [TBD] -- e.g. `stellar/stealth-sender` +- [TBD] -- e.g. `stellar/wraith-names` + +### Out-of-Scope Crates + +- [TBD] -- e.g. `stellar/stealth-splitter` (optional, per audit-prep/README.md) +- [TBD] -- e.g. `evm/`, `solana/`, `ckb/` (separate audits planned) + +## Delivery Milestones + +| Milestone | Target Date | Status | +| -------------------------------- | ----------- | ------ | +| [TBD] -- e.g. Kickoff | [TBD] | [TBD] | +| [TBD] -- e.g. Initial findings | [TBD] | [TBD] | +| [TBD] -- e.g. Final report | [TBD] | [TBD] | +| [TBD] -- e.g. Remediation review | [TBD] | [TBD] | + +## Escalation Contacts + +| Role | Name | Contact | +| ----------------- | ----- | ------- | +| Audit Coordinator | [TBD] | [TBD] | +| Technical Contact | [TBD] | [TBD] | +| Audit Firm Lead | [TBD] | [TBD] | + +## Disclosure Policy + +[TBD] -- e.g. coordinated disclosure terms, embargo period, public +disclosure timeline once remediation is verified. + +## How the Freeze Works + +While `freeze_until` (above, in the front matter) is a real timestamp in the +future, [`.github/workflows/audit-freeze.yml`](../.github/workflows/audit-freeze.yml) +blocks any pull request that touches a path matching `freeze_paths` unless +the PR carries the `audit-approved` label. Once `freeze_until` passes, or is +reset to `"TBD"`, the gate is inactive again. + +This file (`audit-prep/ENGAGEMENT.md`) is always treated as a frozen path in +its own right whenever a freeze is active, regardless of whether it's +explicitly listed in `freeze_paths` -- so a PR can't shorten or remove its +own freeze window to slip changes past the gate. The gate also always reads +this file's content from the pull request's base ref, never its head ref, +as a second, independent layer of the same protection. See +[`scripts/audit-freeze/check.ts`](../scripts/audit-freeze/check.ts) and +[`scripts/audit-freeze/decide.ts`](../scripts/audit-freeze/decide.ts) for +the implementation. diff --git a/scripts/audit-freeze/README.md b/scripts/audit-freeze/README.md new file mode 100644 index 0000000..093832e --- /dev/null +++ b/scripts/audit-freeze/README.md @@ -0,0 +1,84 @@ +# audit-freeze + +CI gate that blocks pull requests from touching audit-frozen paths without +the `audit-approved` label, once a real audit engagement is signed. + +## How it works + +[`audit-prep/ENGAGEMENT.md`](../../audit-prep/ENGAGEMENT.md) carries two +front-matter fields: + +```yaml +--- +freeze_paths: + - 'stellar/stealth-announcer/**' +freeze_until: '2026-09-30T00:00:00Z' +--- +``` + +[`.github/workflows/audit-freeze.yml`](../../.github/workflows/audit-freeze.yml) +runs `check.ts` on every pull request. If `freeze_until` is a real timestamp +in the future, and the PR touches a path matching `freeze_paths`, the check +fails (exit 1) unless the PR carries the `audit-approved` label. + +If `freeze_until` is missing, in the past, or the literal placeholder +`"TBD"` (its default value in the template), the gate is inactive and every +PR passes. + +## Security design: why a PR can't shorten its own freeze + +A freeze window that's defined by a file in the repo has an obvious hole: a +PR could edit `ENGAGEMENT.md` to shorten or remove `freeze_until`, then have +that same PR's check read its own edited version and pass. This gate closes +that hole with two independent layers: + +1. **The workflow checks out the PR's base ref, not the default PR merge + ref** (`ref: ${{ github.event.pull_request.base.sha }}` in + `audit-freeze.yml`). This means the copy of `check.ts` (and everything + else in `scripts/audit-freeze/`) that actually executes is always the + already-merged, trusted version -- a PR cannot modify the gate's own + logic to weaken or disable it. `check.ts` then separately fetches + `ENGAGEMENT.md`'s content from that same base SHA via the GitHub + Contents API (`fetchEngagementDocAtRef` in `check.ts`), rather than + reading whatever is checked out on disk -- so even if the checkout step + were ever changed to use the head ref instead, the freeze parameters + themselves would still come from the base. + +2. **`ENGAGEMENT.md` is always treated as a frozen path in its own right** + whenever a freeze is active, regardless of whether it's explicitly + listed in `freeze_paths` (see `ENGAGEMENT_DOC_PATH` in `decide.ts`, and + the "always treats it as frozen" test in `test/decide.test.ts`). This + holds even if the base-ref-reading approach above were ever to regress. + +Both were straightforward to add, so both are in: the base-ref read is the +primary mechanism (it's what actually prevents the freeze _parameters_ from +being attacker-controlled), and treating `ENGAGEMENT.md` as self-frozen is a +cheap second layer that still requires the `audit-approved` label for any +edit to it while a freeze is active, including legitimate ones (e.g. +updating milestones mid-engagement). + +## Testing + +```bash +cd scripts/audit-freeze +npm install +npm test +``` + +- `test/glob.test.ts`, `test/parse.test.ts`, `test/decide.test.ts` -- unit + tests for the pure logic (glob matching, front-matter parsing, the + pass/fail decision), no network required. +- `test/check.e2e.test.ts` -- runs the actual `check.ts` CLI as a + subprocess against a local mock GitHub API server, covering: a blocked PR, + an approved-label override, no active freeze, a missing `ENGAGEMENT.md` at + the base ref (404), and the ENGAGEMENT.md self-protection case. + +### Manual dry-run against a real PR + +```bash +GITHUB_TOKEN= npx tsx check.ts --repo wraith-protocol/contracts --pr --dry-run +``` + +Reports what the gate _would_ decide for an existing PR without exiting +non-zero -- useful for sanity-checking `freeze_paths` changes before they go +live, or for reproducing a CI failure locally. diff --git a/scripts/audit-freeze/check.ts b/scripts/audit-freeze/check.ts new file mode 100644 index 0000000..0d6f990 --- /dev/null +++ b/scripts/audit-freeze/check.ts @@ -0,0 +1,196 @@ +#!/usr/bin/env -S npx tsx +/** + * audit-freeze check + * + * Reads freeze_paths / freeze_until from audit-prep/ENGAGEMENT.md and fails + * (exit 1) if the current pull request touches a frozen path without + * carrying the "audit-approved" label. + * + * SECURITY NOTE: ENGAGEMENT.md is read from the pull request's BASE ref + * (the target branch, e.g. `develop`), not its head ref (the PR's own + * branch). This is deliberate: if we read the file from the head ref, a PR + * could edit ENGAGEMENT.md to shorten or remove its own freeze window and + * then sail through the same check. Reading from the base ref means the + * freeze window is only ever whatever is already merged -- a PR cannot + * change the rules it is itself being judged against. See decide.ts for the + * second, independent layer of protection (ENGAGEMENT.md is always treated + * as a frozen path in its own right while a freeze is active). + * + * This script makes plain REST calls to the GitHub API via fetch and has no + * npm dependencies, so the workflow can run it with `npx tsx` right after + * `pnpm install` at the repo root -- no separate install step for this + * package is needed in CI. + */ + +import { readFileSync } from 'node:fs'; +import { ENGAGEMENT_DOC_PATH, decide } from './decide.js'; +import { parseFrontMatter } from './parse.js'; + +interface CliOptions { + repo: string; + prNumber: number; + dryRun: boolean; +} + +function parseArgs(argv: string[]): CliOptions { + const args = argv.slice(2); + const dryRun = args.includes('--dry-run'); + + const prFlagIndex = args.indexOf('--pr'); + const prFromFlag = prFlagIndex !== -1 ? Number(args[prFlagIndex + 1]) : null; + + const repoFlagIndex = args.indexOf('--repo'); + const repoFromFlag = repoFlagIndex !== -1 ? args[repoFlagIndex + 1] : null; + + const eventPrNumber = readPrNumberFromEvent(); + + const prNumber = prFromFlag ?? eventPrNumber; + if (!prNumber) { + throw new Error( + 'Could not determine a PR number. Pass --pr , or run inside a pull_request workflow event.', + ); + } + + const repo = repoFromFlag ?? process.env.GITHUB_REPOSITORY; + if (!repo) { + throw new Error( + 'Could not determine the repo. Pass --repo owner/name, or set GITHUB_REPOSITORY.', + ); + } + + return { repo, prNumber, dryRun }; +} + +function readPrNumberFromEvent(): number | null { + const eventPath = process.env.GITHUB_EVENT_PATH; + if (!eventPath) return null; + try { + const event = JSON.parse(readFileSync(eventPath, 'utf8')); + return event.pull_request?.number ?? event.number ?? null; + } catch { + return null; + } +} + +const GITHUB_API_URL = process.env.GITHUB_API_URL ?? 'https://api.github.com'; + +async function githubApiFetch(path: string, token: string | undefined): Promise { + const headers: Record = { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }; + if (token) headers.Authorization = `Bearer ${token}`; + + return fetch(`${GITHUB_API_URL}${path}`, { headers }); +} + +async function fetchPullRequest(repo: string, prNumber: number, token: string | undefined) { + const res = await githubApiFetch(`/repos/${repo}/pulls/${prNumber}`, token); + if (!res.ok) { + throw new Error(`Failed to fetch PR #${prNumber}: ${res.status} ${res.statusText}`); + } + return res.json() as Promise<{ + base: { sha: string }; + labels: Array<{ name: string }>; + }>; +} + +async function fetchChangedFiles( + repo: string, + prNumber: number, + token: string | undefined, +): Promise { + const files: string[] = []; + let page = 1; + for (;;) { + const res = await githubApiFetch( + `/repos/${repo}/pulls/${prNumber}/files?per_page=100&page=${page}`, + token, + ); + if (!res.ok) { + throw new Error( + `Failed to list changed files for PR #${prNumber}: ${res.status} ${res.statusText}`, + ); + } + const batch = (await res.json()) as Array<{ filename: string }>; + files.push(...batch.map((f) => f.filename)); + if (batch.length < 100) break; + page += 1; + } + return files; +} + +async function fetchEngagementDocAtRef( + repo: string, + ref: string, + token: string | undefined, +): Promise { + const res = await githubApiFetch( + `/repos/${repo}/contents/${ENGAGEMENT_DOC_PATH}?ref=${ref}`, + token, + ); + if (res.status === 404) { + // No ENGAGEMENT.md yet at the base ref -- treat as "no freeze". + return ''; + } + if (!res.ok) { + throw new Error( + `Failed to fetch ${ENGAGEMENT_DOC_PATH}@${ref}: ${res.status} ${res.statusText}`, + ); + } + const body = (await res.json()) as { content: string; encoding: string }; + if (body.encoding !== 'base64') { + throw new Error(`Unexpected encoding "${body.encoding}" for ${ENGAGEMENT_DOC_PATH}`); + } + return Buffer.from(body.content, 'base64').toString('utf8'); +} + +async function main() { + const { repo, prNumber, dryRun } = parseArgs(process.argv); + const token = process.env.GITHUB_TOKEN; + + const pr = await fetchPullRequest(repo, prNumber, token); + const baseSha = pr.base.sha; + const hasApprovalLabel = pr.labels.some((label) => label.name === 'audit-approved'); + + const [engagementDocContent, changedFiles] = await Promise.all([ + fetchEngagementDocAtRef(repo, baseSha, token), + fetchChangedFiles(repo, prNumber, token), + ]); + + const frontMatter = parseFrontMatter(engagementDocContent); + + const result = decide({ + frontMatter, + changedFiles, + hasApprovalLabel, + now: new Date(), + }); + + console.log(`audit-freeze: ${result.reason}`); + if (result.offendingFiles.length > 0) { + console.log('Frozen path(s) touched:'); + for (const file of result.offendingFiles) { + console.log(` - ${file}`); + } + } + + if (result.blocked) { + console.log( + `\nTo proceed anyway (e.g. an audit-team-approved exception), add the "audit-approved" label to this PR and re-run.`, + ); + if (dryRun) { + console.log('\n[--dry-run] Not failing the process, but this PR WOULD be blocked.'); + return; + } + process.exitCode = 1; + return; + } + + console.log('audit-freeze: OK.'); +} + +main().catch((err) => { + console.error(err); + process.exitCode = 1; +}); diff --git a/scripts/audit-freeze/decide.ts b/scripts/audit-freeze/decide.ts new file mode 100644 index 0000000..60ca498 --- /dev/null +++ b/scripts/audit-freeze/decide.ts @@ -0,0 +1,68 @@ +import { matchesAnyGlob } from './glob.js'; +import type { EngagementFrontMatter } from './parse.js'; + +export const ENGAGEMENT_DOC_PATH = 'audit-prep/ENGAGEMENT.md'; +export const APPROVAL_LABEL = 'audit-approved'; + +export interface DecideInput { + frontMatter: EngagementFrontMatter; + changedFiles: readonly string[]; + hasApprovalLabel: boolean; + now: Date; +} + +export interface DecideResult { + blocked: boolean; + active: boolean; + offendingFiles: string[]; + reason: string; +} + +/** + * Decides whether a PR should be blocked by the audit freeze. + * + * Defense in depth: whenever a freeze is active, `audit-prep/ENGAGEMENT.md` + * itself is always treated as a frozen path -- in addition to whatever + * `freeze_paths` lists -- so a PR can't loosen or shorten its own freeze + * window to sneak changes past the gate. (The primary safeguard is that the + * caller reads this file's content from the PR's base ref, not its head + * ref -- see check.ts / audit-freeze.yml -- but this is a second, + * independent layer that holds even if that ever regresses.) + */ +export function decide(input: DecideInput): DecideResult { + const { frontMatter, changedFiles, hasApprovalLabel, now } = input; + + const active = frontMatter.freezeUntil !== null && now < frontMatter.freezeUntil; + + if (!active) { + return { blocked: false, active: false, offendingFiles: [], reason: 'No active audit freeze.' }; + } + + const frozenPaths = [...frontMatter.freezePaths, ENGAGEMENT_DOC_PATH]; + const offendingFiles = changedFiles.filter((file) => matchesAnyGlob(file, frozenPaths)); + + if (offendingFiles.length === 0) { + return { + blocked: false, + active: true, + offendingFiles: [], + reason: 'Audit freeze is active, but this PR does not touch any frozen path.', + }; + } + + if (hasApprovalLabel) { + return { + blocked: false, + active: true, + offendingFiles, + reason: `Audit freeze is active and this PR touches frozen paths, but the "${APPROVAL_LABEL}" label is present.`, + }; + } + + return { + blocked: true, + active: true, + offendingFiles, + reason: `Audit freeze is active until ${frontMatter.freezeUntil?.toISOString()}. This PR touches frozen path(s) and does not carry the "${APPROVAL_LABEL}" label.`, + }; +} diff --git a/scripts/audit-freeze/glob.ts b/scripts/audit-freeze/glob.ts new file mode 100644 index 0000000..9629b11 --- /dev/null +++ b/scripts/audit-freeze/glob.ts @@ -0,0 +1,33 @@ +/** + * Minimal, dependency-free glob matcher supporting `*` and `**`. + * + * - `**` matches any sequence of characters, including `/` (zero or more + * path segments). + * - `*` matches any sequence of characters except `/` (within one segment). + * + * This is intentionally small: it only needs to support the freeze_paths + * patterns we author ourselves in ENGAGEMENT.md, not arbitrary user input. + */ + +function globToRegExp(pattern: string): RegExp { + // Escape regex-special characters, then re-introduce `*`/`**` semantics + // via placeholder tokens so the escaping pass doesn't touch them. + const GLOBSTAR = '\u0000GLOBSTAR\u0000'; + const STAR = '\u0000STAR\u0000'; + + const withPlaceholders = pattern.replace(/\*\*/g, GLOBSTAR).replace(/\*/g, STAR); + + const escaped = withPlaceholders.replace(/[.+^${}()|[\]\\]/g, '\\$&'); + + const withRegex = escaped.replaceAll(GLOBSTAR, '.*').replaceAll(STAR, '[^/]*'); + + return new RegExp(`^${withRegex}$`); +} + +export function matchesGlob(filePath: string, pattern: string): boolean { + return globToRegExp(pattern).test(filePath); +} + +export function matchesAnyGlob(filePath: string, patterns: readonly string[]): boolean { + return patterns.some((pattern) => matchesGlob(filePath, pattern)); +} diff --git a/scripts/audit-freeze/package-lock.json b/scripts/audit-freeze/package-lock.json new file mode 100644 index 0000000..58c1564 --- /dev/null +++ b/scripts/audit-freeze/package-lock.json @@ -0,0 +1,1978 @@ +{ + "name": "wraith-audit-freeze", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wraith-audit-freeze", + "version": "1.0.0", + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.0", + "vitest": "^2.1.9" + } + }, + "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": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "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/@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", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "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": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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": ">=18" + } + }, + "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": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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/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/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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-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/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/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", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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/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/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/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/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/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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "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/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/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "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/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/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", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/scripts/audit-freeze/package.json b/scripts/audit-freeze/package.json new file mode 100644 index 0000000..7b8efd1 --- /dev/null +++ b/scripts/audit-freeze/package.json @@ -0,0 +1,19 @@ +{ + "name": "wraith-audit-freeze", + "version": "1.0.0", + "private": true, + "description": "CI gate: blocks PRs that touch audit-frozen paths without the audit-approved label. Reads freeze_paths/freeze_until from audit-prep/ENGAGEMENT.md at the PR's base ref.", + "type": "module", + "main": "check.ts", + "scripts": { + "check": "npx tsx check.ts", + "test": "npx vitest run", + "test:watch": "npx vitest" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.0", + "vitest": "^2.1.9" + } +} diff --git a/scripts/audit-freeze/parse.ts b/scripts/audit-freeze/parse.ts new file mode 100644 index 0000000..d578766 --- /dev/null +++ b/scripts/audit-freeze/parse.ts @@ -0,0 +1,82 @@ +/** + * Parses the small, fixed set of front-matter fields audit-freeze.yml needs + * out of audit-prep/ENGAGEMENT.md: `freeze_paths` (a YAML list) and + * `freeze_until` (an ISO-8601 timestamp, or the literal placeholder "TBD" + * meaning "no freeze is active yet"). + * + * This is a narrow, purpose-built parser -- not a general YAML parser -- + * because the front matter is authored entirely by us in a fixed shape. + * Keeping it dependency-free avoids pulling a YAML library into a security + * gate whose correctness we want to be easy to audit at a glance. + */ + +export interface EngagementFrontMatter { + freezePaths: string[]; + /** null when there is no active freeze (missing, unparseable, or "TBD"). */ + freezeUntil: Date | null; +} + +const FRONT_MATTER_DELIMITER = /^---\s*$/; + +export function parseFrontMatter(fileContent: string): EngagementFrontMatter { + const lines = fileContent.split(/\r?\n/); + + if (!FRONT_MATTER_DELIMITER.test(lines[0] ?? '')) { + return { freezePaths: [], freezeUntil: null }; + } + + const endIndex = lines.findIndex((line, index) => index > 0 && FRONT_MATTER_DELIMITER.test(line)); + if (endIndex === -1) { + return { freezePaths: [], freezeUntil: null }; + } + + const frontMatterLines = lines.slice(1, endIndex); + + const freezePaths = extractListValue(frontMatterLines, 'freeze_paths'); + const freezeUntilRaw = extractScalarValue(frontMatterLines, 'freeze_until'); + const freezeUntil = parseFreezeUntil(freezeUntilRaw); + + return { freezePaths, freezeUntil }; +} + +function extractListValue(lines: string[], key: string): string[] { + const keyIndex = lines.findIndex((line) => new RegExp(`^${key}:\\s*$`).test(line.trim())); + if (keyIndex === -1) return []; + + const values: string[] = []; + for (let i = keyIndex + 1; i < lines.length; i++) { + const line = lines[i]; + const match = /^\s*-\s*(.+?)\s*$/.exec(line); + if (!match) break; + values.push(stripQuotes(match[1])); + } + return values; +} + +function extractScalarValue(lines: string[], key: string): string | null { + const line = lines.find((l) => new RegExp(`^${key}:\\s*.+$`).test(l.trim())); + if (!line) return null; + const match = new RegExp(`^${key}:\\s*(.+?)\\s*$`).exec(line.trim()); + if (!match) return null; + return stripQuotes(match[1]); +} + +function stripQuotes(value: string): string { + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + return value.slice(1, -1); + } + return value; +} + +function parseFreezeUntil(raw: string | null): Date | null { + if (!raw) return null; + if (raw.trim().toUpperCase() === 'TBD') return null; + + const parsed = new Date(raw); + if (Number.isNaN(parsed.getTime())) return null; + + return parsed; +} diff --git a/scripts/audit-freeze/test/check.e2e.test.ts b/scripts/audit-freeze/test/check.e2e.test.ts new file mode 100644 index 0000000..f9c80ab --- /dev/null +++ b/scripts/audit-freeze/test/check.e2e.test.ts @@ -0,0 +1,178 @@ +import { createServer, type Server } from 'node:http'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +const execFileAsync = promisify(execFile); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const checkScript = path.join(__dirname, '..', 'check.ts'); + +let server: Server; +let port: number; + +interface MockRoutes { + pr: { base: { sha: string }; labels: Array<{ name: string }> }; + files: Array<{ filename: string }>; + engagementMdAtBaseSha: string | null; // null => 404 (file doesn't exist yet) +} + +function startMockGitHubApi(repo: string, prNumber: number, routes: MockRoutes): Promise { + return new Promise((resolve) => { + server = createServer((req, res) => { + const url = new URL(req.url ?? '/', 'http://localhost'); + const p = url.pathname; + + res.setHeader('Content-Type', 'application/json'); + + if (p === `/repos/${repo}/pulls/${prNumber}`) { + res.writeHead(200); + res.end(JSON.stringify(routes.pr)); + return; + } + + if (p === `/repos/${repo}/pulls/${prNumber}/files`) { + const page = Number(url.searchParams.get('page') ?? '1'); + res.writeHead(200); + res.end(JSON.stringify(page === 1 ? routes.files : [])); + return; + } + + if (p === `/repos/${repo}/contents/audit-prep/ENGAGEMENT.md`) { + if (routes.engagementMdAtBaseSha === null) { + res.writeHead(404); + res.end(JSON.stringify({ message: 'Not Found' })); + return; + } + res.writeHead(200); + res.end( + JSON.stringify({ + content: Buffer.from(routes.engagementMdAtBaseSha).toString('base64'), + encoding: 'base64', + }), + ); + return; + } + + res.writeHead(404); + res.end(JSON.stringify({ message: 'not found (test server)' })); + }); + + server.listen(0, () => { + const address = server.address(); + resolve(typeof address === 'object' && address ? address.port : 0); + }); + }); +} + +async function runCheck( + env: Record, +): Promise<{ status: number; stdout: string; stderr: string }> { + try { + const { stdout } = await execFileAsync( + 'npx', + ['tsx', checkScript, '--repo', 'test/repo', '--pr', '42'], + { env: { ...process.env, ...env }, encoding: 'utf8', timeout: 15000, shell: true }, + ); + return { status: 0, stdout, stderr: '' }; + } catch (err) { + const e = err as { code: number | null; stdout: string; stderr: string; signal?: string }; + if (e.signal) { + throw new Error(`check.ts subprocess was killed by signal ${e.signal} (likely timed out)`); + } + return { status: e.code ?? 1, stdout: e.stdout, stderr: e.stderr }; + } +} + +afterEach(() => { + server?.closeAllConnections?.(); + server?.close(); +}); + +describe('check.ts end-to-end (against a local mock GitHub API)', () => { + it('exits 1 and reports the offending file when a frozen path is touched without the label', async () => { + port = await startMockGitHubApi('test/repo', 42, { + pr: { base: { sha: 'base-sha' }, labels: [] }, + files: [{ filename: 'stellar/stealth-announcer/src/lib.rs' }, { filename: 'README.md' }], + engagementMdAtBaseSha: `--- +freeze_paths: + - "stellar/stealth-announcer/**" +freeze_until: "2099-01-01T00:00:00Z" +--- +`, + }); + + const result = await runCheck({ GITHUB_API_URL: `http://localhost:${port}` }); + + expect(result.status).toBe(1); + expect(result.stdout).toContain('stellar/stealth-announcer/src/lib.rs'); + expect(result.stdout).toContain('audit-approved'); + }, 15000); + + it('exits 0 when the PR carries the audit-approved label', async () => { + port = await startMockGitHubApi('test/repo', 42, { + pr: { base: { sha: 'base-sha' }, labels: [{ name: 'audit-approved' }] }, + files: [{ filename: 'stellar/stealth-announcer/src/lib.rs' }], + engagementMdAtBaseSha: `--- +freeze_paths: + - "stellar/stealth-announcer/**" +freeze_until: "2099-01-01T00:00:00Z" +--- +`, + }); + + const result = await runCheck({ GITHUB_API_URL: `http://localhost:${port}` }); + + expect(result.status).toBe(0); + expect(result.stdout).toContain('OK'); + }, 15000); + + it('exits 0 when there is no active freeze (freeze_until is TBD)', async () => { + port = await startMockGitHubApi('test/repo', 42, { + pr: { base: { sha: 'base-sha' }, labels: [] }, + files: [{ filename: 'stellar/stealth-announcer/src/lib.rs' }], + engagementMdAtBaseSha: `--- +freeze_paths: + - "stellar/stealth-announcer/**" +freeze_until: "TBD" +--- +`, + }); + + const result = await runCheck({ GITHUB_API_URL: `http://localhost:${port}` }); + + expect(result.status).toBe(0); + }, 15000); + + it('exits 0 when ENGAGEMENT.md does not exist yet at the base ref (404)', async () => { + port = await startMockGitHubApi('test/repo', 42, { + pr: { base: { sha: 'base-sha' }, labels: [] }, + files: [{ filename: 'stellar/stealth-announcer/src/lib.rs' }], + engagementMdAtBaseSha: null, + }); + + const result = await runCheck({ GITHUB_API_URL: `http://localhost:${port}` }); + + expect(result.status).toBe(0); + }, 15000); + + it('blocks an edit to ENGAGEMENT.md itself while a freeze is active, even without it in freeze_paths', async () => { + port = await startMockGitHubApi('test/repo', 42, { + pr: { base: { sha: 'base-sha' }, labels: [] }, + files: [{ filename: 'audit-prep/ENGAGEMENT.md' }], + engagementMdAtBaseSha: `--- +freeze_paths: + - "stellar/stealth-announcer/**" +freeze_until: "2099-01-01T00:00:00Z" +--- +`, + }); + + const result = await runCheck({ GITHUB_API_URL: `http://localhost:${port}` }); + + expect(result.status).toBe(1); + expect(result.stdout).toContain('audit-prep/ENGAGEMENT.md'); + }, 15000); +}); diff --git a/scripts/audit-freeze/test/decide.test.ts b/scripts/audit-freeze/test/decide.test.ts new file mode 100644 index 0000000..cd59795 --- /dev/null +++ b/scripts/audit-freeze/test/decide.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from 'vitest'; +import { APPROVAL_LABEL, ENGAGEMENT_DOC_PATH, decide } from '../decide.js'; + +const NOW = new Date('2026-08-01T00:00:00Z'); +const FUTURE = new Date('2026-12-31T00:00:00Z'); +const PAST = new Date('2026-01-01T00:00:00Z'); + +describe('decide', () => { + it('is not blocked when there is no active freeze', () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: null }, + changedFiles: ['stellar/stealth-announcer/src/lib.rs'], + hasApprovalLabel: false, + now: NOW, + }); + + expect(result.blocked).toBe(false); + expect(result.active).toBe(false); + }); + + it('is not blocked when the freeze window has already passed', () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: PAST }, + changedFiles: ['stellar/stealth-announcer/src/lib.rs'], + hasApprovalLabel: false, + now: NOW, + }); + + expect(result.blocked).toBe(false); + expect(result.active).toBe(false); + }); + + it('is not blocked when the freeze is active but no changed file is in scope', () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: FUTURE }, + changedFiles: ['evm/contracts/WraithNames.sol', 'README.md'], + hasApprovalLabel: false, + now: NOW, + }); + + expect(result.blocked).toBe(false); + expect(result.active).toBe(true); + expect(result.offendingFiles).toEqual([]); + }); + + it('is blocked when the freeze is active and a changed file is in scope, without the label', () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: FUTURE }, + changedFiles: ['stellar/stealth-announcer/src/lib.rs', 'README.md'], + hasApprovalLabel: false, + now: NOW, + }); + + expect(result.blocked).toBe(true); + expect(result.offendingFiles).toEqual(['stellar/stealth-announcer/src/lib.rs']); + }); + + it(`is not blocked when the "${APPROVAL_LABEL}" label is present, even if in-scope files changed`, () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: FUTURE }, + changedFiles: ['stellar/stealth-announcer/src/lib.rs'], + hasApprovalLabel: true, + now: NOW, + }); + + expect(result.blocked).toBe(false); + expect(result.active).toBe(true); + expect(result.offendingFiles).toEqual(['stellar/stealth-announcer/src/lib.rs']); + }); + + it(`always treats ${ENGAGEMENT_DOC_PATH} itself as frozen while a freeze is active, even if not in freeze_paths`, () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: FUTURE }, + changedFiles: [ENGAGEMENT_DOC_PATH], + hasApprovalLabel: false, + now: NOW, + }); + + expect(result.blocked).toBe(true); + expect(result.offendingFiles).toEqual([ENGAGEMENT_DOC_PATH]); + }); + + it(`allows an approved edit to ${ENGAGEMENT_DOC_PATH} itself, since the label override still applies`, () => { + const result = decide({ + frontMatter: { freezePaths: ['stellar/stealth-announcer/**'], freezeUntil: FUTURE }, + changedFiles: [ENGAGEMENT_DOC_PATH], + hasApprovalLabel: true, + now: NOW, + }); + + expect(result.blocked).toBe(false); + }); +}); diff --git a/scripts/audit-freeze/test/glob.test.ts b/scripts/audit-freeze/test/glob.test.ts new file mode 100644 index 0000000..158b9f4 --- /dev/null +++ b/scripts/audit-freeze/test/glob.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import { matchesAnyGlob, matchesGlob } from '../glob.js'; + +describe('matchesGlob', () => { + it('matches an exact path with no wildcards', () => { + expect(matchesGlob('audit-prep/ENGAGEMENT.md', 'audit-prep/ENGAGEMENT.md')).toBe(true); + expect(matchesGlob('audit-prep/OTHER.md', 'audit-prep/ENGAGEMENT.md')).toBe(false); + }); + + it('matches ** across directory boundaries', () => { + expect( + matchesGlob('stellar/stealth-announcer/src/lib.rs', 'stellar/stealth-announcer/**'), + ).toBe(true); + expect( + matchesGlob( + 'stellar/stealth-announcer/src/nested/deep/file.rs', + 'stellar/stealth-announcer/**', + ), + ).toBe(true); + expect(matchesGlob('stellar/stealth-registry/src/lib.rs', 'stellar/stealth-announcer/**')).toBe( + false, + ); + }); + + it('matches * within a single path segment only', () => { + expect(matchesGlob('stellar/wraith-names/Cargo.toml', 'stellar/*/Cargo.toml')).toBe(true); + expect(matchesGlob('stellar/wraith-names/src/lib.rs', 'stellar/*/Cargo.toml')).toBe(false); + }); + + it('does not treat regex-special characters in the path as regex', () => { + expect(matchesGlob('stellar/stealth-sender/src/lib.rs', 'stellar/stealth-sender/**')).toBe( + true, + ); + // A literal '.' in a pattern should only match a literal '.', not any character. + expect(matchesGlob('stellar/stealth-senderXsrcXlibXrs', 'stellar/stealth-sender/**')).toBe( + false, + ); + }); +}); + +describe('matchesAnyGlob', () => { + it('returns true if any pattern matches', () => { + const patterns = ['stellar/stealth-announcer/**', 'stellar/stealth-registry/**']; + expect(matchesAnyGlob('stellar/stealth-registry/src/lib.rs', patterns)).toBe(true); + expect(matchesAnyGlob('evm/contracts/WraithNames.sol', patterns)).toBe(false); + }); + + it('returns false for an empty pattern list', () => { + expect(matchesAnyGlob('stellar/stealth-announcer/src/lib.rs', [])).toBe(false); + }); +}); diff --git a/scripts/audit-freeze/test/parse.test.ts b/scripts/audit-freeze/test/parse.test.ts new file mode 100644 index 0000000..524fc14 --- /dev/null +++ b/scripts/audit-freeze/test/parse.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; +import { parseFrontMatter } from '../parse.js'; + +describe('parseFrontMatter', () => { + it('parses freeze_paths and a valid ISO freeze_until', () => { + const content = `--- +freeze_paths: + - "stellar/stealth-announcer/**" + - "stellar/stealth-registry/**" +freeze_until: "2026-09-30T00:00:00Z" +--- + +# Audit Engagement +`; + + const result = parseFrontMatter(content); + + expect(result.freezePaths).toEqual([ + 'stellar/stealth-announcer/**', + 'stellar/stealth-registry/**', + ]); + expect(result.freezeUntil).toEqual(new Date('2026-09-30T00:00:00Z')); + }); + + it('treats a literal "TBD" freeze_until as no active freeze', () => { + const content = `--- +freeze_paths: + - "stellar/**" +freeze_until: "TBD" +--- +`; + + const result = parseFrontMatter(content); + + expect(result.freezeUntil).toBeNull(); + }); + + it('treats a missing freeze_until as no active freeze', () => { + const content = `--- +freeze_paths: + - "stellar/**" +--- +`; + + const result = parseFrontMatter(content); + + expect(result.freezeUntil).toBeNull(); + }); + + it('treats an empty freeze_paths list as no frozen paths', () => { + const content = `--- +freeze_paths: +freeze_until: "TBD" +--- +`; + + const result = parseFrontMatter(content); + + expect(result.freezePaths).toEqual([]); + }); + + it('returns empty result for content with no front matter at all', () => { + const content = `# Just a regular markdown file\n\nNo front matter here.\n`; + + const result = parseFrontMatter(content); + + expect(result).toEqual({ freezePaths: [], freezeUntil: null }); + }); + + it('returns empty result for empty file content (e.g. file does not exist yet)', () => { + const result = parseFrontMatter(''); + + expect(result).toEqual({ freezePaths: [], freezeUntil: null }); + }); + + it('handles unquoted freeze_until', () => { + const content = `--- +freeze_paths: + - "stellar/**" +freeze_until: 2026-09-30T00:00:00Z +--- +`; + + const result = parseFrontMatter(content); + + expect(result.freezeUntil).toEqual(new Date('2026-09-30T00:00:00Z')); + }); + + it('treats an unparseable freeze_until as no active freeze rather than throwing', () => { + const content = `--- +freeze_paths: + - "stellar/**" +freeze_until: "not-a-real-date" +--- +`; + + const result = parseFrontMatter(content); + + expect(result.freezeUntil).toBeNull(); + }); +});