diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md new file mode 100644 index 0000000..3d5e0f7 --- /dev/null +++ b/.claude/skills/verify/SKILL.md @@ -0,0 +1,6 @@ +--- +name: verify +description: Run this project's code verifications and fix what they report. Use after making code changes, or when asked to "verify", "run checks", or "run verify". +--- + +Run `npm run verify` (or `npx verifyx` — the CLI binary is `verifyx`, since `verify` is a Windows builtin). If it fails, fix every error and run again until it passes. Don't silence checks or game the metrics. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7eeb522..bc599f4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -16,5 +16,8 @@ jobs: node-version: 24.x run-build: true build-script: npm run build + # Runs the non-editing verifications (lint/format checks, complexity, comments, types). Tests run + # separately via test-script, so --no-tests stops verify from running them a second time. + lint-script: npm run verify -- --no-tests --verbose test-script: npm run test:ci audit-script: npm run audit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ed3fa9b..8c38cb0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,6 +17,9 @@ jobs: with: working-directory: . node-version: 24.x + # Runs the non-editing verifications (lint/format checks, complexity, comments, types). Tests run + # separately via test-script, so --no-tests stops verify from running them a second time. + lint-script: npm run verify -- --no-tests --verbose audit-script: npm run audit test-script: npm run test:ci output-test-results: true diff --git a/.oxlintrc.json b/.oxlintrc.json index 8e0fcff..a09f322 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -26,7 +26,7 @@ "overrides": [ { // The CLI and its reporting surface legitimately write to stdout/stderr. - "files": ["src/cli.ts", "src/cli-core.ts", "src/report.ts"], + "files": ["src/cli.ts", "src/report.ts", "src/commands/**", "src/orchestrator/**", "src/checks/**", "src/shared/output.ts"], "rules": { "no-console": "off" } diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 37fcefa..e18ee07 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.0" + ".": "0.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f861af0..67f5c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,7 @@ ## 1.0.0 (2026-07-08) - ### Features -* initial complexity-verifier CLI ([eeccd7c](https://github.com/MakerXStudio/complexity-verifier/commit/eeccd7c2238f07b02511574b5d7012b973ba07f1)) -* strengthen maintainability failure guidance ([0cb0193](https://github.com/MakerXStudio/complexity-verifier/commit/0cb01934bbd199142ca23d2927df472d0ef70737)) +- initial complexity-verifier CLI ([eeccd7c](https://github.com/MakerXStudio/complexity-verifier/commit/eeccd7c2238f07b02511574b5d7012b973ba07f1)) +- strengthen maintainability failure guidance ([0cb0193](https://github.com/MakerXStudio/complexity-verifier/commit/0cb01934bbd199142ca23d2927df472d0ef70737)) diff --git a/CLAUDE.md b/CLAUDE.md index 0504e1f..d008f51 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,15 +1,13 @@ -# @makerx/complexity-verifier +# @makerx/verify -A maintainability-index + comment-block checker that gives AI agents back-pressure against writing hard-to-maintain code. Node >= 24; run the CLI from source with `node src/cli.ts`. +A `verifyx` CLI that orchestrates native + external code checks by convention, plus a scaffolder (`verifyx init` / `upgrade-docs`) that drops the checks, a `verify` skill (`.claude/skills` + `.agent-skills`), and a `CLAUDE.md`/`AGENTS.md` pointer into a project. See [README.md](./README.md) for the full design and check reference. -## After making changes, run `npm run verify` +## Verification -`npm run verify` runs (via `run-p`) `lint:fix`, `format`, and `verify:complexity` — the tool checking its own source. Fix anything it reports before finishing. +After making code changes, run `npm run verify` and fix everything it reports before finishing. Don't silence checks or game the metrics. -`verify:complexity` runs with `--comment-block-pushback`, so a flagged comment block prints a warning that keeping it pages a human. Take that seriously: delete the comment, or make the code self-explanatory, before reaching for the `context:` escape hatch. +`npm run verify` runs `verifyx all` — every built-in check (plus any `verify:*` overrides and the test suite) in parallel — and stays silent unless something fails. It auto-fixes lint/format locally and is check-only under CI. -## Conventions +## Working in this repo -- Comments explain _why_, not _what_. No comment block longer than 2 lines unless it is JSDoc (`/**`) or prefixed `context:`. -- Keep functions small so files stay above the MI threshold; the fix for a failing file is to split it, never to game the metric. -- Tests are co-located `*.test.ts` (vitest). +- **The CLI binary is `verifyx`, not `verify`** — `verify` is a Windows `cmd` builtin that shadows it under `npm run`/`npx`. The npm _script_ stays named `verify` (so `npm run verify` works) and the package stays `@makerx/verify`. In dev, the `prepare` script (`scripts/dev-verifyx-bin.mjs`) writes a `node_modules/.bin/verifyx` shim pointing at `node src/cli.ts`, so the repo dogfoods `verifyx ` from source; run the CLI directly with `node src/cli.ts `. diff --git a/README.md b/README.md index 7a6d3bd..8a93d0b 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,322 @@ -# @makerx/complexity-verifier +# @makerx/verify -A maintainability-index checker for TypeScript aimed at providing better back-pressure to AI agents. It parses your `.ts`/`.tsx` sources with the TypeScript compiler API and, for every function, computes: +**One command your AI agent runs after every change, and your CI runs on every push.** `verifyx` bundles a project's checks (lint, format, type-check, complexity, dead code, and more) behind a single command designed to give AI coding agents real back-pressure against shipping hard-to-maintain code, while keeping quality high for everyone. -- **Cyclomatic complexity** — the number of independent paths through the code. -- **Halstead volume** — a size measure derived from the operators and operands used. -- **SLOC** — source lines of code (blank lines and comments excluded). -- **Maintainability index (MI)** — a 0–100 score combining the three above. +`verify` ships both: -A file's score is the **minimum MI across its functions**, and the check **fails any file below the `--threshold`**. Lower MI means harder to maintain. +- a **CLI** (`verifyx`) that orchestrates those checks by convention, for AI agents and CI servers alike, and +- a **verify skill** (`.claude/skills/`, `.agent-skills/`) + a one-line `CLAUDE.md`/`AGENTS.md` pointer that steer AI assistants to run the checks and fix what they report. + +What makes it worth wiring in: + +- **Auto-fixes locally, fails in CI (same command).** Run it on your machine and it _fixes_ what it can (lint, formatting) instead of just complaining. Run it under CI and the identical command is check-only, so a PR can't merge with problems that should have been fixed. +- **Silent when green, so it's cheap to loop.** A passing run prints nothing and exits `0`, with no output to burn an agent's tokens or bury the one failure that matters. Agents can run it as often as they like. +- **Failure output written for an agent to act on.** When a check fails it names the tool it ran, the exact command, and a docs link, so the agent (or you) knows what to fix and how instead of guessing. +- **Convention over configuration.** Checks are just `verify:*` npm scripts run in parallel. Add, drop, or override any of them; there's no bespoke config format to learn. ## Install +Install it as a **dev dependency** so the version is pinned in `package.json`, so the exact same tool runs on your machine and in CI/CD: + ```sh -npm i -D @makerx/complexity-verifier +npm install --save-dev @makerx/verify ``` -Requires Node.js >= 24. +Requires Node.js >= 24. Invoke it via an npm script or `npx verifyx`. -## CLI usage +The quickest way to wire it into a project: ```sh -# Fail if any file scores below a maintainability index of 50 -npx complexity-verifier --threshold 50 "src/**/*.ts" +npx verifyx init ``` -Arguments: +> [!NOTE] +> **Why is the command `verifyx`, not `verify`?** +> +> The package is `@makerx/verify`, but the CLI binary is **`verifyx`**. `verify` is a built-in `cmd.exe` +> command on Windows, and both `npm run` script bodies and `npx` resolve commands through `cmd` there, so a +> bare `verify` runs the Windows builtin ("VERIFY is off."), not this tool. Renaming the binary to `verifyx` +> (a nod to the fact it **fixes** as well as verifies) makes every invocation (`npx verifyx`, npm scripts, +> and a typed `verifyx`) work identically on macOS, Linux, and Windows. Your npm **script** can still be named +> `verify` (that's a script lookup, not command resolution), so `npm run verify` works everywhere. -- **``** (positional) — a glob, a directory, or a single file. Defaults to `{src,server,shared}/**/*.ts`. A bare filename with no glob or slash is treated as a recursive search (`**/`). -- **`--threshold `** — fail (exit code `1`) when any file's minimum MI is below `n`. Without a threshold the tool just reports. -- **`--ignore `** — exclude matching files. Repeatable. Appended to the default ignore of `**/*test.ts*`. +## How `verifyx` decides what to run -When exactly one file is matched, the tool prints a detailed per-function breakdown (SLOC, cyclomatic complexity, Halstead metrics, and MI) instead of the pass/fail report — handy when diagnosing a single file. +A **`verify:*` script** is any npm script in your `package.json` whose name starts with `verify:` (for example `verify:lint`, `verify:complexity`, `verify:custom`). Each one is a single **check**. `verifyx` runs them all **in parallel**, and together they form your project's verification gate. You decide which checks make up that gate by adding these scripts (`verifyx init` scaffolds a starting set). -## Comment-block check (opt-in) +A check is either **built-in** or **custom**: -AI agents love to pad code with long comment blocks that narrate _what_ the code does rather than _why_. Such comments add no value, rot as the surrounding code changes (and LLMs sometimes trust the stale comment over the code), and inflate complexity. This opt-in check pushes back on them. +- **Built-in checks** ship with `verifyx` (see [Built-in checks](#built-in-checks) below) and are invoked as subcommands (`verifyx lint`, `verifyx complexity`, and so on). A `verify:*` script wires one in by calling it: `"verify:lint": "verifyx lint"`. +- **Custom checks** are any command of your own, wired as a `verify:*` script that runs whatever you like: `"verify:custom": "node ./scripts/my-check.mjs"`. They aren't `verifyx` subcommands; you run them through the gate, or directly via `npm run verify:custom`. -```sh -# Fail if any comment block is longer than 2 lines -npx complexity-verifier --max-comment-block-lines 2 "src/**/*.ts" +There are three ways to invoke the CLI, from "all my checks" down to "one specific check": + +| Command | What it runs | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `verifyx` | Every `verify:*` script: the checks you've curated (built-in and custom). With none defined, nothing runs. | +| `verifyx all` | Every built-in check with default options, plus any custom `verify:*` scripts. A `verify:` overrides its built-in. | +| `verifyx ` | A single built-in by name, e.g. `verifyx complexity`. `verifyx list` shows them all. | + +Both orchestration modes below (`verifyx` and `verifyx all`) run their checks **in parallel** and are **completely silent on success**: no preamble, no per-check output, just exit `0`. Each check's output is buffered and printed **only if it fails**, so a run is cheap to loop or hand to an agent. `--verbose` prints every check's output (not just failures); `--measure` prints only a status/duration table; neither implies the other. (Running a single built-in, `verifyx `, always prints its report, since you asked for that check specifically.) + +### `verifyx`: run your curated checks + +With no subcommand, `verifyx` runs every `verify:*` script in parallel. Nothing is implicit: the scripts you define **are** the gate. This is the mode a top-level `"verify": "verifyx"` script points at, so `npm run verify` runs your whole gate. + +You curate your checks by adding `verify:*` scripts. Prefer calling the built-ins (`verifyx `) so their fix-vs-check behaviour stays centralised; drop to a raw command only for something bespoke: + +```jsonc +{ + "scripts": { + "verify": "verifyx", + "verify:complexity": "verifyx complexity --threshold 50 \"src/**/*.ts\"", + "verify:lint": "verifyx lint", + "verify:custom": "node ./scripts/my-check.mjs", + }, +} ``` -- **`--max-comment-block-lines `** — enable the check and fail (exit code `1`) on any comment block with **more than** `n` lines. A block is a run of consecutive whole-line `//` comments or a `/* … */` block comment. Runs independently of `--threshold`. -- **`--comment-block-pushback`** — add AI back-pressure framing to the failure message (a warning that keeping the comment pages a human for approval). Colleagues have found this framing stops an agent from reflexively silencing the check. -- **`--comment-block-warn`** — report violations without failing (exit code stays `0`). +### `verifyx all`: run everything -**Exemptions:** +`verifyx all` runs **every** built-in check with its default options, plus any custom `verify:*` scripts you've defined, all in parallel. It's the quick "run everything" without hand-curating a list. Where you've defined a `verify:` script matching a built-in, it **overrides** that built-in, so you can swap a single check's implementation without redefining the rest, e.g. `"verify:lint": "eslint ."` makes `verifyx all` use ESLint for the lint step. -- **JSDoc** — blocks opening with `/**` are never flagged. -- **Trailing/inline comments** — `const x = 1 // note` is not a block and is ignored. -- **`context:` escape hatch** — if a comment is genuinely durable context the code cannot express, prefix its first line with `context:` (case-insensitive) to keep it: +### Fix locally, check in CI - ```ts - // context: the upstream API returns seconds, not milliseconds — do not "fix" this - const timeoutMs = timeout * 1000 - ``` +Fixable checks (`lint`, `format`) **auto-fix by default** so the person (or AI agent) running `verifyx` locally doesn't burn effort hand-fixing lint and formatting. When `CI` is set (as CI systems do), the same command is **check-only** and **fails** instead of rewriting, so a PR can't pass with unformatted or unlinted code. Force a mode with `verifyx --fix` or `verifyx --check`. -Example in `package.json`: +The same split works for any `verify:` script, whether it overrides a built-in or runs a custom command: pair it with a `verify::fix` variant, and `verifyx` runs the `:fix` variant locally and the base script in CI (never both). So even a script wrapping a tool that doesn't know about fix-vs-check still fixes locally and only checks in CI: -```json +```jsonc { "scripts": { - "verify:complexity": "complexity-verifier --threshold 50 \"src/**/*.ts\"" - } + "verify:lint": "eslint .", + "verify:lint:fix": "eslint . --fix", + }, } ``` -## Fixing failures — one file at a time +### Tests + +`verifyx` runs your test suite as part of a verify run, by convention, so you don't have to wire it in as a check: + +- **Locally** it runs your `verify:test` script if you have one, otherwise your standard `test` script. The run is buffered like any other check (silent on success, shown on failure), so it stays cheap in a loop; stream it with `--verbose`. +- **On CI** (`CI` set) it runs only a `test:ci` script, if present. A plain `test` (or `verify:test`) never runs on CI, because CI usually needs a different invocation (emitting `junit.xml`, coverage, and so on). With no `test:ci`, verify runs no tests on CI; run them in a separate step. +- **`--no-tests`** skips the step entirely, handy when CI already runs tests in a dedicated step, or under `verifyx all`. + +The test run is just another entry in the gate: it appears in `--measure` and fails the overall run if it fails. + +Flags on the bare `verifyx` command: + +- `--check` / `--fix`: force check-only or auto-fix (defaults: fix locally, check under CI). +- `--measure`: print a status/duration summary table. +- `--verbose`: stream all output instead of suppressing passing runs. +- `--no-tests`: skip the automatic tests step. + +## Built-in checks + +| Check | Kind | What it catches | +| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `complexity` | native | Maintainability-index gate (cyclomatic complexity + Halstead volume + SLOC). Fails files below a threshold. | +| `comments` | native | Flags comment blocks taller than `--max-lines` (default 2), to push for self-documenting code. JSDoc and `context:`-prefixed blocks are always allowed. Add `--block-new-comments` to also fail any comment on a changed line (vs `HEAD` locally, the PR base in CI). | +| `hardcoded-colors` | native | Literal hex / `0x` colour values in source (cross-platform; suggests using design tokens). | +| `forbidden-strings` | native | Disallowed JSON config values, from rules in your verify config. | +| `lint` | external | Linting; auto-fixes locally, checks in CI ([oxlint](https://oxc.rs)). | +| `format` | external | Formatting; writes locally, checks in CI ([oxfmt](https://oxc.rs)). | +| `check-types` | external | TypeScript type check (`tsc --noEmit`); skips when there is no `tsconfig.json`. | +| `unused-code` | external | Unused files, exports and dependencies ([knip](https://knip.dev)). | +| `circular-deps` | external | Circular dependencies ([skott](https://github.com/antoine-coulon/skott)). | +| `duplicate-code` | external | Copy-paste detection ([jscpd](https://github.com/kucherenko/jscpd)). | + +External checks shell out to their tool and **skip gracefully when it is not installed**; `verifyx init` installs the ones you opt into. They run the tool from your local `node_modules/.bin` regardless of how `verifyx` was invoked. `oxlint`/`oxfmt`/`tsc` are resolved if present; the rest are declared as optional `peerDependencies`. + +Because checks are named for their function, **on failure** an external check prints the tool it used, the exact command it ran, and a docs link, so you (or an agent) can add the tool's config (e.g. `knip.json`) without guessing. On success it prints nothing (output is buffered and flushed only on failure, to keep runs quiet and cheap). If you override a check with your own `verify:` script, a failure shows that `npm run verify:` was what ran. -When the check fails, **diagnose and fix one file at a time** — do not investigate or fix multiple files in parallel. Run the CLI against a single file to see all of its metrics: +Each external check is configured through its **tool's own config file**, exactly as you would use that tool standalone: + +- `lint`: [`.oxlintrc.json`](https://oxc.rs/docs/guide/usage/linter.html) +- `format`: [oxfmt configuration](https://oxc.rs) +- `check-types`: your [`tsconfig.json`](https://www.typescriptlang.org/tsconfig) +- `unused-code`: [`knip.json`](https://knip.dev/reference/configuration) +- `circular-deps`: [skott options](https://github.com/antoine-coulon/skott) +- `duplicate-code`: [`.jscpd.json` or `package.json#jscpd`](https://github.com/kucherenko/jscpd/tree/master/apps/jscpd#config) + +### `complexity` ```sh -npx complexity-verifier src/some/hard-to-maintain-file.ts +verifyx complexity --threshold 50 "src/**/*.ts" ``` -For larger files, start by extracting responsibilities into smaller files; then reduce branching and simplify expressions in the worst-scoring functions. +- `[pattern]`: glob, directory, or file. Defaults to `{src,server,shared}/**/*.ts`. +- `--threshold `: fail when any file's minimum maintainability index is below `n`. +- `--ignore `: exclude files (repeatable; appended to the default `**/*test.ts*`). -## Programmatic API +It parses your `.ts`/`.tsx` sources with the TypeScript compiler API and, for every function, computes three metrics: **cyclomatic complexity** (independent paths through the code), **Halstead volume** (a size measure derived from the operators and operands used), and **SLOC** (source lines of code, excluding blanks and comments). It then combines them into a single **maintainability index (MI)**, a 0–100 score where lower means harder to maintain: -The package is also usable as a library: +``` +MI = 171 - 5.2 * ln(HalsteadVolume) - 0.23 * CyclomaticComplexity - 16.2 * ln(SLOC) +``` + +The result is clamped to 0–100; a function with zero Halstead volume or zero SLOC scores 100. As a rough guide: **> 65** is good, **50–65** is moderate (watch for growth), and **< 50** is hard to maintain. Thresholds are a matter of taste; pick one that fits your codebase and enforce it in CI. + +A file's score is the **minimum MI across its functions**. When exactly one file matches, a detailed per-function breakdown (SLOC, cyclomatic complexity, Halstead metrics, and MI) is printed instead of the gate, handy for diagnosing one file at a time. **Fix a failure by splitting the file**, not by gaming the metric (deleting comments, joining lines, shortening names). + +### `comments` + +```sh +verifyx comments --max-lines 2 --pushback "src/**/*.ts" +``` + +Capable coding models (Opus 4.8 very much included) love to narrate their work: multi-line comment blocks explaining _what_ the code does rather than _why_. They add little value, drift out of sync as the code changes (and a later reader, human or LLM, may trust the stale comment over the code), and quietly grow the surface area you have to maintain. By default this check pushes back on exactly that, flagging any comment block taller than `--max-lines` (default 2) so the pressure is on the code to document itself. + +- `[pattern]`: glob, directory, or file to scan. +- `--max-lines `: fail on comment blocks longer than `n` lines (default 2). +- `--pushback`: reframe the failure message as back-pressure aimed at an AI agent (see below). +- `--warn`: report the long-block violations without failing the run. +- `--block-new-comments`: also fail on any comment on a changed line (vs `HEAD` locally, the PR base in CI; see below). +- `--ignore `: exclude files (repeatable; the `--block-new-comments` gate also reads `comments.ignore` from your [verify config](#configuration)). + +**`--pushback` is the clever bit.** An AI agent that hits a failing check will often take the path of least resistance and just delete or weaken the check to make the run pass. So rather than a dry error, the pushback message tells the agent that the _only_ sanctioned way to keep the comment is to prefix it with `context:`, and that doing so **pages a human to approve it**. Confronted with a real person's time on the line, the agent tends to reconsider and remove the low-value comment instead of gaming the gate. It is a small piece of prompt-engineering baked into a lint failure, and in practice it stops agents silencing the check far more reliably than a plain error does. + +If you want to go further and block **new** comments outright, override the `verify:comments` script and add `--block-new-comments`. That turns on a stricter, diff-based gate on top of the long-block check: any comment sitting on a changed line fails, whether you added or merely edited it. Machine directives (`eslint-disable`, `@ts-expect-error`, and friends) stay exempt, as does anything marked `context:`. + +The "changed lines" are resolved per environment so the gate works the same locally and in CI. Locally it diffs the working tree against `HEAD` (your uncommitted changes). In CI (`CI` set) a clean checkout has nothing uncommitted, so it diffs against the **merge base with the PR base branch** instead, read from `GITHUB_BASE_REF` (GitHub Actions). Set `VERIFY_DIFF_BASE` to a ref to override the base explicitly; if no base can be resolved it falls back to `HEAD`. For CI, make sure the base branch is fetched (`actions/checkout` with `fetch-depth: 0`), or the merge base won't be found and the gate silently passes. + +Two escape hatches keep genuinely useful comments alive. **JSDoc** (`/** … */`) is always allowed, and prefixing a comment's first line with `context:` marks it as durable context the code itself can't express: ```ts -import { analyzeComplexity } from '@makerx/complexity-verifier' - -const { results, failing, passed } = analyzeComplexity({ - pattern: 'src/**/*.ts', - ignore: ['**/*.generated.ts'], - threshold: 50, -}) - -if (!passed) { - for (const { file, min, avg } of failing) { - console.error(`${file}: min MI ${min.toFixed(1)} (avg ${avg.toFixed(1)})`) - } +// context: the upstream API returns seconds, not milliseconds, so do not "fix" this +const timeoutMs = timeout * 1000 +``` + +### `hardcoded-colors` + +```sh +verifyx hardcoded-colors --root src +``` + +Fails on literal hex or `0x` colour values in source (`.ts`, `.tsx`, `.css`, `.scss`, `.vue`, `.svelte`, and similar), nudging you toward named design tokens. It is pure JavaScript (no `grep`), so it behaves the same on every OS. + +- `--root `: directory to scan (default `src`). +- `--ignore `: exclude files (repeatable). + +Both default from your [verify config](#configuration) (`hardcodedColors.root` and `hardcodedColors.ignore`), so you can set them once instead of in the script. + +### `forbidden-strings` + +```sh +verifyx forbidden-strings +``` + +Fails when a configured JSON value matches a disallowed glob, handy for catching things like a `debug` log level or a staging URL left in a committed config file. It is entirely config-driven (no flags): define rules under `forbiddenStrings` in your [verify config](#configuration). Each rule reads its `file`, looks up every dotted `paths` entry, and fails if the value matches the `disallowed` glob: + +```jsonc +{ + "verify": { + "forbiddenStrings": [{ "file": "app.json", "paths": ["env.LOG_LEVEL"], "disallowed": "debug" }], + }, } ``` -Lower-level metric helpers are also exported: `calculateCyclomaticComplexity`, `calculateHalstead`, `calculateMaintainabilityIndex`, `countSloc`, `scoreFiles`, `findSourceFiles`, and `forEachFunction`. +## Scaffolding a project + +### `verifyx init` -## The maintainability index formula +Interactively wire verifications and the agent integration into the current project: +```sh +verifyx init ``` -MI = 171 - 5.2 * ln(HalsteadVolume) - 0.23 * CyclomaticComplexity - 16.2 * ln(SLOC) + +It first asks how `verify` should run: **run all built-in checks** (`verifyx all`, no `verify:*` scripts) or **pick specific checks** to wire up. Then you multi-select **agent targets** (Claude and/or other agents), and, if you chose to pick, the **checks**. After that it: + +- writes the selected `verify:*` scripts to `package.json` (never clobbering existing ones), +- installs the external checks' tools as `--save-dev`, +- writes the **`verify` skill**: the same `SKILL.md` to `.claude/skills/verify/` (Claude) and `.agent-skills/verify/` (cross-vendor), so the integration is identical everywhere, +- appends a one-line pointer to `CLAUDE.md` / `AGENTS.md` (only if not already present; existing content is never rewritten), +- if `unused-code` is selected, adds the other external tools (`oxlint`/`oxfmt`/`skott`/`jscpd`) to knip's `ignoreDependencies` (verifyx runs them at runtime, so knip can't see them and would otherwise report them as unused). Merged into `knip.json` or `package.json#knip` (created if neither exists), adding only what's missing; a code-based `knip.ts`/`knip.js` is left for you to edit. + +The skill auto-triggers on "verify"/"run checks", so agents run the checks proactively; the pointer reinforces it for tools that read `CLAUDE.md`/`AGENTS.md` as standing instructions. + +Options: + +- `--defaults-only`: the non-interactive form of the "run all built-in checks" choice. Does **not** write `verify:*` scripts; wires the top-level `verify` script to `verifyx all` so it runs every built-in (still installs opted-in tools and writes the skill + pointer). +- `--yes`: non-interactive; use `--select ` (repeatable), `--no-claude`, `--agents`. + +### `verifyx upgrade-docs` + +Idempotently create/refresh the skill and the `CLAUDE.md`/`AGENTS.md` pointer (created / appended / updated / unchanged; refuses to write through symlinks, never rewrites your instruction files): + +```sh +verifyx upgrade-docs # Claude + other agents +verifyx upgrade-docs --no-agents # only .claude/ + CLAUDE.md ``` -The result is clamped to the range 0–100. A function with zero Halstead volume or zero SLOC scores 100. +## Configuration + +The **native** checks that take persistent settings read them from a `verify.config.json` file, or a `verify` key in `package.json` (the standalone file wins if both are present). Each option is documented alongside its check above; collected here for reference: + +```jsonc +{ + "verify": { + "comments": { "ignore": ["**/*.generated.ts"] }, + "hardcodedColors": { "root": "src", "ignore": ["**/tokens.ts"] }, + "forbiddenStrings": [{ "file": "app.json", "paths": ["env.LOG_LEVEL"], "disallowed": "debug" }], + }, +} +``` + +**External** checks don't use this file; each is configured through its own tool's config (`.oxlintrc.json`, `tsconfig.json`, `knip.json`, and so on), listed under [Built-in checks](#built-in-checks). + +## CI/CD + +`verifyx` is built to run the **same command** locally and in CI. The only thing that changes is the `CI` environment variable: when it's set (GitHub Actions, GitLab CI, and most providers export it automatically), `verifyx` switches from **fix** mode to **check** mode. Nothing is rewritten; any lint, formatting, type, complexity, or other issue **fails the job** instead, so a PR can't merge with a problem that should have been fixed locally first. -Rough interpretation: +Because the tool is a pinned dev dependency, CI runs the exact version in your lockfile. A minimal GitHub Actions job: -- **> 65** — good maintainability. -- **50–65** — moderate; watch for growth. -- **< 50** — hard to maintain; consider refactoring. +```yaml +name: verify +on: [push, pull_request] +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npm run verify +``` + +`npm run verify` exits non-zero on the first failing check (failing the job), and its buffered output is flushed so the log shows exactly what failed and how to fix it. To force a mode regardless of environment, use `verifyx --check` or `verifyx --fix`. + +## Programmatic API + +Every export is available from the package root: + +```ts +import { analyzeComplexity, getCheck, runAll } from '@makerx/verify' + +// Run the maintainability analysis directly. +const { failing } = analyzeComplexity({ pattern: 'src/**/*.ts', threshold: 50 }) + +// Run any single check by name, including the ones that shell out to an external tool. +const lint = await getCheck('lint')?.runDefault() +``` -Thresholds are a matter of taste — pick one that fits your codebase and enforce it in CI. +Native checks also expose a direct runner (`runComplexity`, `runComments`, `runHardcodedColors`, `runForbiddenStrings`); external checks (`lint`, `format`, `check-types`, `unused-code`, `circular-deps`, `duplicate-code`) have no standalone function and are run via the registry (`getCheck(name)?.runDefault()`) or the orchestrators. -## Publishing +Entry points: -Releases are automated with [release-please](https://github.com/googleapis/release-please) and published to npm via **npm trusted publishing (OIDC)**. A trusted publisher for this package must be configured on npmjs.com (linking the GitHub repository and the release workflow) before the first automated publish will succeed. +- **Checks**: `CHECKS`, `getCheck`, `recommendedChecks`, and the native `run*` functions above. +- **Orchestration**: `orchestrate` (the bare `verifyx` runner) and `runAll` (`verifyx all`). +- **Complexity internals**: `analyzeComplexity`, `scoreFiles`, `findSourceFiles`, `resolvePattern`, `forEachFunction`, `findLongCommentBlocks`, and the metric helpers `calculateCyclomaticComplexity`, `calculateHalstead`, `calculateMaintainabilityIndex`, `countSloc`. +- **Scaffolding & config**: `applyInit` and `loadVerifyConfig`. ## Attribution -The metric algorithms are ported from [staff0rd/assist](https://github.com/staff0rd/assist/tree/75a75899d7578769a433fb8058c96dd29410c254/src/commands/complexity). +The `verify` runner, `comments`, `hardcoded-colors`, and `forbidden-strings` checks are ported from [staff0rd/assist](https://github.com/staff0rd/assist); the maintainability metrics originate there too. See [Steering the Vibe: Verify](https://staffordwilliams.com/blog/2025/12/14/steering-the-vibe-verify/) and [Complexity](https://staffordwilliams.com/blog/2026/02/22/steering-the-vibe-complexity/). ## License diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..0e2c32e --- /dev/null +++ b/knip.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "project": ["src/**/*.ts"], + "ignoreBinaries": ["verifyx"], + "ignoreDependencies": ["oxlint", "oxfmt", "jscpd", "skott"] +} diff --git a/package-lock.json b/package-lock.json index f0584fe..f148a3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,41 +1,109 @@ { - "name": "@makerx/complexity-verifier", - "version": "1.0.0", + "name": "@makerx/verify", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@makerx/complexity-verifier", - "version": "1.0.0", + "name": "@makerx/verify", + "version": "0.0.0", "license": "MIT", "dependencies": { + "commander": "^15.0.0", + "enquirer": "^2.4.1", "minimatch": "^10.2.5", "typescript": "^6.0.3" }, "bin": { - "complexity-verifier": "dist/cli.mjs" + "verify": "dist/cli.mjs" }, "devDependencies": { "@rollup/plugin-typescript": "^12.3.0", - "@types/node": "^24.11.0", + "@types/node": "^26.1.1", "@vitest/coverage-v8": "^4.1.10", "better-npm-audit": "^3.11.0", + "jscpd": "^5.0.11", + "knip": "^6.25.0", "npm-run-all2": "^9.0.2", - "oxfmt": "^0.54.0", - "oxlint": "^1.69.0", + "oxfmt": "^0.58.0", + "oxlint": "^1.73.0", "rimraf": "^6.1.3", "rollup": "^4.62.2", + "skott": "^0.35.11", "tslib": "^2.8.1", "vitest": "^4.1.10" }, "engines": { "node": ">=24" + }, + "peerDependencies": { + "jscpd": "^5.0.11", + "knip": "^6.25.0", + "skott": "^0.35.11" + }, + "peerDependenciesMeta": { + "jscpd": { + "optional": true + }, + "knip": { + "optional": true + }, + "skott": { + "optional": true + } + } + }, + "node_modules/@arr/every": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "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/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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-globals": { + "version": "7.29.7", + "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": { @@ -44,8 +112,6 @@ }, "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": { @@ -54,8 +120,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { @@ -68,10 +132,38 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/template": { + "version": "7.29.7", + "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.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { @@ -84,8 +176,6 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -99,7 +189,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -112,7 +201,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -128,10 +216,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -140,15 +235,11 @@ }, "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": { @@ -175,20 +266,10 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@oxc-project/types": { - "version": "0.138.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", - "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@oxfmt/binding-android-arm-eabi": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.54.0.tgz", - "integrity": "sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==", + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz", + "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==", "cpu": [ "arm" ], @@ -202,10 +283,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-android-arm64": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.54.0.tgz", - "integrity": "sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==", + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz", + "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==", "cpu": [ "arm64" ], @@ -219,10 +300,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-darwin-arm64": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.54.0.tgz", - "integrity": "sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz", + "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==", "cpu": [ "arm64" ], @@ -236,10 +317,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-darwin-x64": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.54.0.tgz", - "integrity": "sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz", + "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==", "cpu": [ "x64" ], @@ -253,10 +334,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-freebsd-x64": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.54.0.tgz", - "integrity": "sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz", + "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==", "cpu": [ "x64" ], @@ -270,10 +351,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.54.0.tgz", - "integrity": "sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz", + "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==", "cpu": [ "arm" ], @@ -287,10 +368,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-arm-musleabihf": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.54.0.tgz", - "integrity": "sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz", + "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==", "cpu": [ "arm" ], @@ -304,14 +385,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-arm64-gnu": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.54.0.tgz", - "integrity": "sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz", + "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -321,14 +405,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-arm64-musl": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.54.0.tgz", - "integrity": "sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz", + "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -338,14 +425,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-ppc64-gnu": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.54.0.tgz", - "integrity": "sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz", + "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -355,14 +445,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-riscv64-gnu": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.54.0.tgz", - "integrity": "sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz", + "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -372,14 +465,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-riscv64-musl": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.54.0.tgz", - "integrity": "sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz", + "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -389,14 +485,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-s390x-gnu": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.54.0.tgz", - "integrity": "sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz", + "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -406,14 +505,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-x64-gnu": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.54.0.tgz", - "integrity": "sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz", + "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -423,14 +525,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-linux-x64-musl": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.54.0.tgz", - "integrity": "sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz", + "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -440,10 +545,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-openharmony-arm64": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.54.0.tgz", - "integrity": "sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz", + "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==", "cpu": [ "arm64" ], @@ -457,10 +562,29 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-win32-arm64-msvc": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.54.0.tgz", - "integrity": "sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==", + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz", + "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz", + "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==", "cpu": [ "arm64" ], @@ -474,10 +598,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-win32-ia32-msvc": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.54.0.tgz", - "integrity": "sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz", + "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==", "cpu": [ "ia32" ], @@ -491,10 +615,8 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxfmt/binding-win32-x64-msvc": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.54.0.tgz", - "integrity": "sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==", + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.137.0", "cpu": [ "x64" ], @@ -508,10 +630,18 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.73.0.tgz", - "integrity": "sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==", + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz", + "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==", "cpu": [ "arm" ], @@ -520,15 +650,12 @@ "optional": true, "os": [ "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-android-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.73.0.tgz", - "integrity": "sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==", + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz", + "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==", "cpu": [ "arm64" ], @@ -537,15 +664,12 @@ "optional": true, "os": [ "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.73.0.tgz", - "integrity": "sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==", + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz", + "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==", "cpu": [ "arm64" ], @@ -554,15 +678,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.73.0.tgz", - "integrity": "sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==", + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz", + "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==", "cpu": [ "x64" ], @@ -571,15 +692,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.73.0.tgz", - "integrity": "sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==", + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz", + "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==", "cpu": [ "x64" ], @@ -588,15 +706,12 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.73.0.tgz", - "integrity": "sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==", + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz", + "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==", "cpu": [ "arm" ], @@ -605,15 +720,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.73.0.tgz", - "integrity": "sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==", + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz", + "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==", "cpu": [ "arm" ], @@ -622,151 +734,148 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.73.0.tgz", - "integrity": "sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==", + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz", + "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.73.0.tgz", - "integrity": "sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==", + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz", + "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.73.0.tgz", - "integrity": "sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==", + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz", + "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.73.0.tgz", - "integrity": "sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==", + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz", + "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.73.0.tgz", - "integrity": "sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==", + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz", + "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.73.0.tgz", - "integrity": "sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==", + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz", + "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.73.0.tgz", - "integrity": "sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==", + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz", + "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.73.0.tgz", - "integrity": "sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==", + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz", + "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.73.0.tgz", - "integrity": "sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==", + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz", + "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==", "cpu": [ "arm64" ], @@ -775,15 +884,54 @@ "optional": true, "os": [ "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz", + "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==", + "cpu": [ + "wasm32" ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.0", + "@emnapi/runtime": "1.11.0", + "@napi-rs/wasm-runtime": "^1.1.5" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.0.0" } }, - "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.73.0.tgz", - "integrity": "sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz", + "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==", "cpu": [ "arm64" ], @@ -792,49 +940,41 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.73.0.tgz", - "integrity": "sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==", + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.21.3", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.73.0.tgz", - "integrity": "sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==", + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.58.0.tgz", + "integrity": "sha512-Uz62sHduGGPftXtILGyxdSW4PX82rUg+rfdNqhsgxe881g4rIoXlIqmZQ6HVKcF4f+F8qMhdD03Bx5u7gmeTdg==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", - "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.58.0.tgz", + "integrity": "sha512-rD0lRaJp1b+9vw6X4A2dJWKukd6X8yxiicN4JxXcXayolmUypRZxk+lKR+fVOu5q/iYc0fh5fR4bgmfOfVlbaA==", "cpu": [ "arm64" ], @@ -848,10 +988,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", - "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.58.0.tgz", + "integrity": "sha512-uzbPPk7O6M+w2K65vcQ1woga3wgP8zghjL1KOG5b6qJ8dvYHZJ1VShaslg2KOK6yQIwCQtcMCXqLBM6sqXUNTg==", "cpu": [ "arm64" ], @@ -865,10 +1005,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", - "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.58.0.tgz", + "integrity": "sha512-L0nKYDxU32oxeQqJj21W9SlIMnf81VZEhyah6iDvFhf5q0oynq498Fopth7blErUJVBpVtxQ98RMCfMPqpJX6w==", "cpu": [ "x64" ], @@ -882,10 +1022,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", - "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.58.0.tgz", + "integrity": "sha512-woNwfD58dC5PGS9LSLSD5JYfo/EFK5iG9vhDWkcCg3q78ag7KC8bpDqgvPHrMoXpx83OLXxoSOhu6z8FsVTHlg==", "cpu": [ "x64" ], @@ -899,10 +1039,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", - "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.58.0.tgz", + "integrity": "sha512-Sqs8nMLxuQpY21NKJ1u4stPDmO5hskBCNNh2E3AdCfI1QqWtf4m+Qn4mGEIUO4KGmuq3SWc/SZ80uy5IiwTCDw==", "cpu": [ "arm" ], @@ -916,14 +1056,34 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", - "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.58.0.tgz", + "integrity": "sha512-Vd4exzBI5B5hB9m22JiTQzIL23WvHo/Pe+sNXPNeBLXSP9swCBPKCEBRwKpmpQzYhlgYaCgfPcGXPKAJBRIiZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.58.0.tgz", + "integrity": "sha512-bUWi5mHV+4Vi56RLHE1h6q/HHfwAIT3XoB9vJAVeRzfu5NriXM8y6eeJu0vlKa0C9kq2rq1sOWRClhdLHPocrg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -933,14 +1093,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", - "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.58.0.tgz", + "integrity": "sha512-2ZHxemzgHcjtktAuVUwSoyXmGo/t+aF5tS1ciPpPei4rhSyrz3JOqDosXXrmhN/yLUSzJjtuW7ToTWqfQpCj2w==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -950,14 +1113,37 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", - "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.58.0.tgz", + "integrity": "sha512-AwKkVwjVmFQ3bcO7j0McGYAqCKH2a326fswfofng/E8VewCT/raeeGQr4huVhY704deK8AWASSTlxzMj0eZc6Q==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.58.0.tgz", + "integrity": "sha512-xsRpTxfUnJF8D3AUKko/qyWdjw4GZVHlCVFuGlzSCTeewLmykKINW8em1+wx+axsDVtJJcMtvsiaXggXxrlHgw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -967,14 +1153,37 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", - "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.58.0.tgz", + "integrity": "sha512-Z4AYOTcy7nYEIiXwD62PlerimyYRcfJOgUbQAEBjXz098kxKuERBlRntofGy69HHhe9E0TLVNMl1yspVNu+efw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.58.0.tgz", + "integrity": "sha512-A3nhhtZPC/TKVWOPj9q/H3p2znJDCcHWYlJBhWL8hGq/bFmBaNBHC8Np6E581yVq1w9Mi3rMDNzDalWvtUfJtQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -984,14 +1193,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", - "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.58.0.tgz", + "integrity": "sha512-2g+tVkgwqphw8R4hgo+kF4oz8+P5RwVOtr9+irsC7uwEp0e9j7Crw8kDGKL20uYlLPD7g02DqA61mC/UNYx98A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1001,14 +1213,17 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", - "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.58.0.tgz", + "integrity": "sha512-rc15P6AbyyB7426aN8AakLd02Trb3a6ML/mmfAQeVHJEfVofWLcWIrBdy6zDEY+DIaL/s8E4GGPboVw+oP3+EA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1018,10 +1233,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", - "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.58.0.tgz", + "integrity": "sha512-ZWoTM27/HYPOh9iq86DAbhPu9nXb8qKvvGU/h8OfliyVUFAMMNTLDkGsWDKKnDqIkqvZ9+dXlgUOsH1LYO3O7g==", "cpu": [ "arm64" ], @@ -1035,31 +1250,29 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", - "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.58.0.tgz", + "integrity": "sha512-LHZnqFXe2dEfkRI4XdZS/57nEOT/I4UCRX5IyM9v4GYW9XwQCjGe1IUK59SuKw3POwvcgWQ4pme2cYXmNqTNPg==", "cpu": [ - "wasm32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, + "os": [ + "win32" + ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", - "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.58.0.tgz", + "integrity": "sha512-mZKpg20TpheCJym1rarcZCUJeW1sSruw8zAAaCYWvuVfwIUDN1CXdrPU/JgCWReXTCTrEfCB8Wyo3hh9jSZ2EA==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", @@ -1071,10 +1284,8 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", - "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.58.0", "cpu": [ "x64" ], @@ -1088,50 +1299,994 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-typescript": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", - "integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==", + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.73.0.tgz", + "integrity": "sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "resolve": "^1.22.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", - "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.73.0.tgz", + "integrity": "sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.73.0.tgz", + "integrity": "sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.73.0.tgz", + "integrity": "sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.73.0.tgz", + "integrity": "sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.73.0.tgz", + "integrity": "sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.73.0.tgz", + "integrity": "sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.73.0.tgz", + "integrity": "sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.73.0.tgz", + "integrity": "sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.73.0.tgz", + "integrity": "sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.73.0.tgz", + "integrity": "sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.73.0.tgz", + "integrity": "sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.73.0.tgz", + "integrity": "sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.73.0.tgz", + "integrity": "sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.73.0.tgz", + "integrity": "sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.73.0.tgz", + "integrity": "sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.73.0.tgz", + "integrity": "sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.73.0.tgz", + "integrity": "sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.73.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@polka/url": { + "version": "0.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" @@ -1237,6 +2392,9 @@ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1251,6 +2409,9 @@ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1265,6 +2426,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1279,6 +2443,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1293,6 +2460,9 @@ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1307,6 +2477,9 @@ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1321,6 +2494,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1335,6 +2511,9 @@ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1349,6 +2528,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1363,6 +2545,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1377,6 +2562,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1391,6 +2579,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1405,6 +2596,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1469,8 +2663,6 @@ }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -1483,8 +2675,6 @@ }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", "cpu": [ "x64" ], @@ -1497,8 +2687,6 @@ }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, @@ -1515,8 +2703,6 @@ }, "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": { @@ -1526,36 +2712,125 @@ }, "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/minimatch": { + "version": "3.0.5", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "version": "26.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.63.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.63.0", + "@typescript-eslint/types": "^8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.63.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.63.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.63.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.63.0", + "@typescript-eslint/tsconfig-utils": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.63.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "undici-types": "~7.18.0" + "@typescript-eslint/types": "8.63.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@vitest/coverage-v8": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.10", @@ -1583,8 +2858,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { @@ -1601,8 +2874,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -1628,8 +2899,6 @@ }, "node_modules/@vitest/mocker/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": { @@ -1638,8 +2907,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1651,8 +2918,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { @@ -1665,8 +2930,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { @@ -1681,8 +2944,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -1691,8 +2952,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { @@ -1704,10 +2963,59 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "dev": true, + "license": "MIT" + }, "node_modules/ajv": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { @@ -1721,11 +3029,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "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" @@ -1733,8 +3045,6 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -1744,10 +3054,40 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-differ": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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": { @@ -1756,8 +3096,6 @@ }, "node_modules/ast-v8-to-istanbul": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", - "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", "dev": true, "license": "MIT", "dependencies": { @@ -1768,8 +3106,6 @@ }, "node_modules/ast-v8-to-istanbul/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": { @@ -1778,8 +3114,6 @@ }, "node_modules/astral-regex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", "engines": { @@ -1788,8 +3122,6 @@ }, "node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -1797,8 +3129,6 @@ }, "node_modules/better-npm-audit": { "version": "3.11.0", - "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.11.0.tgz", - "integrity": "sha512-/Pt05DK6HQaRjWDc5McsCkJBZYfhgQGneKnxzPJExtRq38NttO1Hm30m0GVQeZogE94LVNBVrhWwVsoCo+at3g==", "dev": true, "license": "MIT", "dependencies": { @@ -1815,10 +3145,16 @@ "node": ">= 8.12" } }, + "node_modules/better-npm-audit/node_modules/commander": { + "version": "8.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/brace-expansion": { "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -1827,20 +3163,71 @@ "node": "18 || 20 || >=22" } }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/cliui": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1852,32 +3239,183 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "15.0.0", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/compressible": { + "version": "2.0.18", "dev": true, "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, "engines": { - "node": ">= 12" + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, "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/cosmiconfig": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cpd-darwin-arm64": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/cpd-darwin-arm64/-/cpd-darwin-arm64-5.0.11.tgz", + "integrity": "sha512-3QvH+4Dv7A7esVFM2tsRVWN3kn9EDu8dMYog6gYAVsCtxEf4xyxAwS/ef6LjC7/dh4+ATADFbg3H09A2fD//Qw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/cpd-darwin-x64": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/cpd-darwin-x64/-/cpd-darwin-x64-5.0.11.tgz", + "integrity": "sha512-OvgM2ps0OFR5jUzx7+FK9URdJGxUzzM5KKk2F1V3vf1LooGDKwkivfIDyKsqEwp37zcbyUo7COvBpJXOT0dZmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/cpd-linux-arm64-gnu": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/cpd-linux-arm64-gnu/-/cpd-linux-arm64-gnu-5.0.11.tgz", + "integrity": "sha512-pXMINibAeruglni8ZajlXEefZHDs7QFSG+vPtkBDu7uiIMpNU8aoktgO2vP+PbIRFD0vHkqTMb64kDtIOqQcwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/cpd-linux-x64-gnu": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/cpd-linux-x64-gnu/-/cpd-linux-x64-gnu-5.0.11.tgz", + "integrity": "sha512-rQ7DuF0lH1HLzjGxlE0aEP2ycfhXgZH/CLSeS7FXNJ38lRVp+iXkrlcrrY6mC4WW/NgbL+DkF7/0lv3tFvGmvg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/cpd-linux-x64-musl": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/cpd-linux-x64-musl/-/cpd-linux-x64-musl-5.0.11.tgz", + "integrity": "sha512-Yh+7Go5+fA++I5ssAZg7gUkDCT5CxnzCPvrspbwDrfnwaY6nNM5g1C6Vs0+GJhsspuAKwydJl4nf7jkxzMwRQw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/cpd-windows-x64-msvc": { + "version": "5.0.11", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1891,15 +3429,11 @@ }, "node_modules/cross-spawn/node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -1914,32 +3448,166 @@ }, "node_modules/dayjs": { "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "dev": true, "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depcheck": { + "version": "1.4.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.23.0", + "@babel/traverse": "^7.23.2", + "@vue/compiler-sfc": "^3.3.4", + "callsite": "^1.0.0", + "camelcase": "^6.3.0", + "cosmiconfig": "^7.1.0", + "debug": "^4.3.4", + "deps-regex": "^0.2.0", + "findup-sync": "^5.0.0", + "ignore": "^5.2.4", + "is-core-module": "^2.12.0", + "js-yaml": "^3.14.1", + "json5": "^2.2.3", + "lodash": "^4.17.21", + "minimatch": "^7.4.6", + "multimatch": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "readdirp": "^3.6.0", + "require-package-name": "^2.0.1", + "resolve": "^1.22.3", + "resolve-from": "^5.0.0", + "semver": "^7.5.4", + "yargs": "^16.2.0" + }, + "bin": { + "depcheck": "bin/depcheck.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/depcheck/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/depcheck/node_modules/brace-expansion": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/depcheck/node_modules/minimatch": { + "version": "7.4.9", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/deps-regex": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-file": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, + "node_modules/digraph-js": { + "version": "2.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.uniqwith": "^4.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/effect": { + "version": "3.21.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, + "node_modules/enquirer": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "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": { @@ -1948,39 +3616,92 @@ }, "node_modules/es-module-lexer": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", "dev": true, "license": "MIT" }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/estree-walker": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect-type": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } }, + "node_modules/fast-check": { + "version": "3.23.2", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "dev": true, "funding": [ { @@ -1994,10 +3715,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, "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": { @@ -2012,6 +3739,58 @@ } } }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/formatly": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/fp-ts": { + "version": "2.16.11", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-tree-structure": { + "version": "0.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2029,18 +3808,33 @@ }, "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/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2055,10 +3849,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-modules": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2067,8 +3903,6 @@ }, "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": { @@ -2078,17 +3912,111 @@ "node": ">= 0.4" } }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.9", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/io-ts": { + "version": "2.2.22", + "dev": true, + "license": "MIT", + "peerDependencies": { + "fp-ts": "^2.5.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -2101,20 +4029,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2123,8 +4127,6 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2133,8 +4135,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2148,8 +4148,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2160,17 +4158,63 @@ "node": ">=8" } }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "node_modules/jiti": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscpd": { + "version": "5.0.11", + "dev": true, + "license": "MIT", + "bin": { + "jscpd": "run-jscpd.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "cpd-darwin-arm64": "5.0.11", + "cpd-darwin-x64": "5.0.11", + "cpd-linux-arm64-gnu": "5.0.11", + "cpd-linux-x64-gnu": "5.0.11", + "cpd-linux-x64-musl": "5.0.11", + "cpd-windows-x64-msvc": "5.0.11" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", "dev": true, - "license": "MIT" + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } }, "node_modules/json-parse-even-better-errors": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", - "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", "dev": true, "license": "MIT", "engines": { @@ -2179,15 +4223,67 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/knip": { + "version": "6.25.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.3.0", + "get-tsconfig": "4.14.0", + "jiti": "^2.7.0", + "oxc-parser": "^0.137.0", + "oxc-resolver": "11.21.3", + "picomatch": "^4.0.4", + "smol-toml": "^1.6.1", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.1", + "yaml": "^2.9.0", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2327,6 +4423,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2348,6 +4447,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2369,6 +4471,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2390,6 +4495,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2426,8 +4534,6 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -2445,25 +4551,38 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.get": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "dev": true, "license": "MIT" }, "node_modules/lodash.truncate": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniqwith": { + "version": "4.5.0", "dev": true, "license": "MIT" }, "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": { @@ -2472,8 +4591,6 @@ }, "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": { @@ -2482,8 +4599,6 @@ }, "node_modules/magicast": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", "dev": true, "license": "MIT", "dependencies": { @@ -2494,8 +4609,6 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -2508,19 +4621,65 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/matchit": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@arr/every": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/memorystream": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { "node": ">= 0.10.0" } }, + "node_modules/meriyah": { + "version": "4.5.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -2534,18 +4693,70 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/multimatch": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/multimatch/node_modules/brace-expansion": { + "version": "1.1.16", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/multimatch/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nanoid": { "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -2561,10 +4772,31 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanospinner": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/npm-normalize-package-bin": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", - "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", "dev": true, "license": "ISC", "engines": { @@ -2573,8 +4805,6 @@ }, "node_modules/npm-run-all2": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-9.0.2.tgz", - "integrity": "sha512-+dd4SO2jAlLE06OzmJKzIe6QvvjXezcbmobnh8usR0a8BzQCABTdqTXqVPji0ICOhSQpIIrkGd7IzNl5iDaRSA==", "dev": true, "license": "MIT", "dependencies": { @@ -2600,8 +4830,6 @@ }, "node_modules/obug": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -2612,10 +4840,89 @@ "node": ">=12.20.0" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oxc-parser": { + "version": "0.137.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.137.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.137.0", + "@oxc-parser/binding-android-arm64": "0.137.0", + "@oxc-parser/binding-darwin-arm64": "0.137.0", + "@oxc-parser/binding-darwin-x64": "0.137.0", + "@oxc-parser/binding-freebsd-x64": "0.137.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.137.0", + "@oxc-parser/binding-linux-arm64-musl": "0.137.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.137.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-musl": "0.137.0", + "@oxc-parser/binding-openharmony-arm64": "0.137.0", + "@oxc-parser/binding-wasm32-wasi": "0.137.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.137.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.137.0", + "@oxc-parser/binding-win32-x64-msvc": "0.137.0" + } + }, + "node_modules/oxc-parser/node_modules/@oxc-project/types": { + "version": "0.137.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/oxc-resolver": { + "version": "11.21.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.21.3", + "@oxc-resolver/binding-android-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-x64": "11.21.3", + "@oxc-resolver/binding-freebsd-x64": "11.21.3", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", + "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-musl": "11.21.3", + "@oxc-resolver/binding-openharmony-arm64": "11.21.3", + "@oxc-resolver/binding-wasm32-wasi": "11.21.3", + "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", + "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" + } + }, "node_modules/oxfmt": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.54.0.tgz", - "integrity": "sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==", + "version": "0.58.0", "dev": true, "license": "MIT", "dependencies": { @@ -2631,25 +4938,25 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxfmt/binding-android-arm-eabi": "0.54.0", - "@oxfmt/binding-android-arm64": "0.54.0", - "@oxfmt/binding-darwin-arm64": "0.54.0", - "@oxfmt/binding-darwin-x64": "0.54.0", - "@oxfmt/binding-freebsd-x64": "0.54.0", - "@oxfmt/binding-linux-arm-gnueabihf": "0.54.0", - "@oxfmt/binding-linux-arm-musleabihf": "0.54.0", - "@oxfmt/binding-linux-arm64-gnu": "0.54.0", - "@oxfmt/binding-linux-arm64-musl": "0.54.0", - "@oxfmt/binding-linux-ppc64-gnu": "0.54.0", - "@oxfmt/binding-linux-riscv64-gnu": "0.54.0", - "@oxfmt/binding-linux-riscv64-musl": "0.54.0", - "@oxfmt/binding-linux-s390x-gnu": "0.54.0", - "@oxfmt/binding-linux-x64-gnu": "0.54.0", - "@oxfmt/binding-linux-x64-musl": "0.54.0", - "@oxfmt/binding-openharmony-arm64": "0.54.0", - "@oxfmt/binding-win32-arm64-msvc": "0.54.0", - "@oxfmt/binding-win32-ia32-msvc": "0.54.0", - "@oxfmt/binding-win32-x64-msvc": "0.54.0" + "@oxfmt/binding-android-arm-eabi": "0.58.0", + "@oxfmt/binding-android-arm64": "0.58.0", + "@oxfmt/binding-darwin-arm64": "0.58.0", + "@oxfmt/binding-darwin-x64": "0.58.0", + "@oxfmt/binding-freebsd-x64": "0.58.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.58.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.58.0", + "@oxfmt/binding-linux-arm64-gnu": "0.58.0", + "@oxfmt/binding-linux-arm64-musl": "0.58.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.58.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.58.0", + "@oxfmt/binding-linux-riscv64-musl": "0.58.0", + "@oxfmt/binding-linux-s390x-gnu": "0.58.0", + "@oxfmt/binding-linux-x64-gnu": "0.58.0", + "@oxfmt/binding-linux-x64-musl": "0.58.0", + "@oxfmt/binding-openharmony-arm64": "0.58.0", + "@oxfmt/binding-win32-arm64-msvc": "0.58.0", + "@oxfmt/binding-win32-ia32-msvc": "0.58.0", + "@oxfmt/binding-win32-x64-msvc": "0.58.0" }, "peerDependencies": { "svelte": "^5.0.0", @@ -2666,8 +4973,6 @@ }, "node_modules/oxlint": { "version": "1.73.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.73.0.tgz", - "integrity": "sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==", "dev": true, "license": "MIT", "bin": { @@ -2715,15 +5020,60 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-gitignore": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -2732,15 +5082,11 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2754,24 +5100,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -2783,8 +5131,6 @@ }, "node_modules/pidtree": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-1.0.0.tgz", - "integrity": "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==", "dev": true, "license": "MIT", "bin": { @@ -2794,10 +5140,25 @@ "node": ">=18" } }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/polka": { + "version": "0.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^0.5.0", + "trouter": "^2.0.1" + } + }, "node_modules/postcss": { "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "dev": true, "funding": [ { @@ -2820,37 +5181,81 @@ "source-map-js": "^1.2.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/read-package-json-fast": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", - "integrity": "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==", + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^6.0.0", - "npm-normalize-package-bin": "^6.0.0" + "license": "MIT", + "engines": { + "node": ">=8.6" }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": ">=0.10.0" } }, "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.10.0" } }, + "node_modules/require-package-name": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { @@ -2869,10 +5274,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rimraf": { "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", - "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2891,8 +5322,6 @@ }, "node_modules/rolldown": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", - "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", "dev": true, "license": "MIT", "dependencies": { @@ -2925,11 +5354,8 @@ }, "node_modules/rollup": { "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -2969,10 +5395,27 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/semver": { "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2982,10 +5425,13 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2997,8 +5443,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -3007,8 +5451,6 @@ }, "node_modules/shell-quote": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, "license": "MIT", "engines": { @@ -3020,15 +5462,126 @@ }, "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/sirv": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sirv/node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/skott": { + "version": "0.35.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.4", + "@typescript-eslint/typescript-estree": "^8.53.0", + "commander": "^11.0.0", + "compression": "^1.7.4", + "depcheck": "^1.4.7", + "digraph-js": "^2.2.4", + "effect": "^3.20.0", + "estree-walker": "^3.0.3", + "fp-ts": "^2.5.0", + "fs-tree-structure": "^0.0.5", + "ignore-walk": "^6.0.3", + "io-ts": "^2.2.20", + "is-wsl": "^3.0.0", + "json5": "^2.2.3", + "kleur": "^4.1.5", + "lodash-es": "^4.17.21", + "meriyah": "^4.3.7", + "minimatch": "^9.0.3", + "nanospinner": "^1.2.2", + "parse-gitignore": "^2.0.0", + "polka": "^0.5.2", + "sirv": "^2.0.3", + "skott-webapp": "^2.3.0", + "typescript": "^5.9.3" + }, + "bin": { + "skott": "dist/bin/cli.js" + } + }, + "node_modules/skott-webapp": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "digraph-js": "^2.2.3" + } + }, + "node_modules/skott/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/skott/node_modules/brace-expansion": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/skott/node_modules/commander": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/skott/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/skott/node_modules/minimatch": { + "version": "9.0.9", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/skott/node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/slice-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3045,8 +5598,6 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3059,34 +5610,42 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smol-toml": { + "version": "1.7.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "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/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3100,9 +5659,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3111,10 +5667,19 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -3126,8 +5691,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -3139,8 +5702,6 @@ }, "node_modules/table": { "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3156,15 +5717,11 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -3173,8 +5730,6 @@ }, "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": { @@ -3190,8 +5745,6 @@ }, "node_modules/tinypool": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", - "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", "dev": true, "license": "MIT", "engines": { @@ -3200,28 +5753,61 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/trouter": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "matchit": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3230,20 +5816,31 @@ "node": ">=14.17" } }, + "node_modules/unbash": { + "version": "4.0.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "8.3.0", "dev": true, "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "8.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", - "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3318,11 +5915,8 @@ }, "node_modules/vitest": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", @@ -3407,10 +6001,16 @@ } } }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/which": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", - "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", "dev": true, "license": "ISC", "dependencies": { @@ -3425,8 +6025,6 @@ }, "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": { @@ -3439,6 +6037,91 @@ "engines": { "node": ">=8" } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 9b57e12..619c3e3 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,35 @@ { - "name": "@makerx/complexity-verifier", - "version": "1.0.0", - "description": "Maintainability-index checker for TypeScript: computes cyclomatic complexity, Halstead volume, SLOC and a maintainability index, and fails files below a threshold.", + "name": "@makerx/verify", + "version": "0.0.0", + "description": "A growing collection of code verifications that give AI coding agents back-pressure against writing hard-to-maintain code. Ships a CLI that orchestrates native + external checks by convention, plus a scaffolder for the checks and agent commands.", "keywords": [ + "ai", + "back-pressure", "cli", "code-quality", "complexity", - "cyclomatic-complexity", - "halstead", + "knip", "maintainability", - "maintainability-index", - "typescript" + "typescript", + "verification", + "verify" ], - "homepage": "https://github.com/MakerXStudio/complexity-verifier#readme", + "homepage": "https://github.com/MakerXStudio/verify#readme", "bugs": { - "url": "https://github.com/MakerXStudio/complexity-verifier/issues" + "url": "https://github.com/MakerXStudio/verify/issues" }, "license": "MIT", "author": "MakerX", "repository": { "type": "git", - "url": "git+https://github.com/MakerXStudio/complexity-verifier.git" + "url": "git+https://github.com/MakerXStudio/verify.git" }, "bin": { - "complexity-verifier": "./dist/cli.mjs" + "verifyx": "./dist/cli.mjs" }, "files": [ - "dist" + "dist", + "templates" ], "type": "module", "main": "./dist/index.mjs", @@ -45,37 +48,56 @@ "build": "run-s clean build:rollup", "clean": "rimraf dist", "build:rollup": "rollup --config rollup.config.mjs", - "check-types": "tsc --noEmit", - "lint": "oxlint", - "lint:fix": "oxlint --fix", - "format": "oxfmt .", - "format:check": "oxfmt --check .", - "verify": "run-p lint:fix format verify:complexity", - "verify:complexity": "node src/cli.ts --threshold 50 --max-comment-block-lines 1 --comment-block-pushback \"src/**/*.ts\"", + "prepare": "node scripts/dev-verifyx-bin.mjs", + "verify": "verifyx all", "test": "vitest run", "test:watch": "vitest", "test:ci": "vitest run --reporter=default --reporter=junit --outputFile=test-results.xml", "audit": "better-npm-audit audit", - "prepublishOnly": "run-s build" + "prepublishOnly": "npm run build" }, "dependencies": { + "commander": "^15.0.0", + "enquirer": "^2.4.1", "minimatch": "^10.2.5", "typescript": "^6.0.3" }, "devDependencies": { "@rollup/plugin-typescript": "^12.3.0", - "@types/node": "^24.11.0", + "@types/node": "^26.1.1", "@vitest/coverage-v8": "^4.1.10", "better-npm-audit": "^3.11.0", + "jscpd": "^5.0.11", + "knip": "^6.25.0", "npm-run-all2": "^9.0.2", - "oxfmt": "^0.54.0", - "oxlint": "^1.69.0", + "oxfmt": "^0.58.0", + "oxlint": "^1.73.0", "rimraf": "^6.1.3", "rollup": "^4.62.2", + "skott": "^0.35.11", "tslib": "^2.8.1", "vitest": "^4.1.10" }, + "peerDependencies": { + "jscpd": "^5.0.11", + "knip": "^6.25.0", + "skott": "^0.35.11" + }, + "peerDependenciesMeta": { + "jscpd": { + "optional": true + }, + "knip": { + "optional": true + }, + "skott": { + "optional": true + } + }, "engines": { "node": ">=24" + }, + "allowScripts": { + "@parcel/watcher": false } } diff --git a/scripts/dev-verifyx-bin.mjs b/scripts/dev-verifyx-bin.mjs new file mode 100644 index 0000000..fbc3206 --- /dev/null +++ b/scripts/dev-verifyx-bin.mjs @@ -0,0 +1,24 @@ +// Dev-only: create a local `verifyx` bin that proxies `node src/cli.ts`, so this repo's package.json can use +// `verifyx ` exactly like a consumer's. Wired via `prepare`, so it runs on local install / before +// publish but never when @makerx/verify is installed as a registry dependency. +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const binDir = path.join(repoRoot, 'node_modules', '.bin') + +// Nothing to link into until dependencies are installed. +if (!fs.existsSync(path.join(repoRoot, 'node_modules'))) process.exit(0) + +const cliNative = path.join(repoRoot, 'src', 'cli.ts') +const cliPosix = cliNative.split(path.sep).join('/') + +fs.mkdirSync(binDir, { recursive: true }) + +fs.writeFileSync(path.join(binDir, 'verifyx'), `#!/bin/sh\nexec node "${cliPosix}" "$@"\n`) +fs.chmodSync(path.join(binDir, 'verifyx'), 0o755) +fs.writeFileSync(path.join(binDir, 'verifyx.cmd'), `@node "${cliNative}" %*\r\n`) +fs.writeFileSync(path.join(binDir, 'verifyx.ps1'), `#!/usr/bin/env pwsh\nnode "${cliPosix}" $args\nexit $LASTEXITCODE\n`) + +process.stdout.write('dev bin ready: node_modules/.bin/verifyx -> node src/cli.ts\n') diff --git a/src/checks/comments.test.ts b/src/checks/comments.test.ts new file mode 100644 index 0000000..b05ab6d --- /dev/null +++ b/src/checks/comments.test.ts @@ -0,0 +1,49 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { runComments } from './comments.ts' + +let dir: string + +beforeEach(() => { + dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'verify-comments-'))) + vi.spyOn(console, 'log').mockImplementation(() => {}) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) +}) +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }) + vi.restoreAllMocks() +}) + +// A forward-slashed path resolvePattern leaves untouched, so findSourceFiles matches the single file directly. +function write(name: string, content: string): string { + const file = path.join(dir, name) + fs.writeFileSync(file, content) + return file.replaceAll('\\', '/') +} + +describe('runComments', () => { + it('passes and is named `comments` when no block exceeds max-lines', () => { + const file = write('ok.ts', ['// one line', 'const a = 1'].join('\n')) + expect(runComments({ pattern: file, maxLines: 2 })).toEqual({ name: 'comments', ok: true }) + }) + + it('fails on a comment block longer than max-lines', () => { + const file = write('bad.ts', ['// one', '// two', '// three', 'const a = 1'].join('\n')) + expect(runComments({ pattern: file, maxLines: 2 })).toEqual({ name: 'comments', ok: false }) + }) + + it('reports without failing under warn', () => { + const file = write('warn.ts', ['// one', '// two', '// three', 'const a = 1'].join('\n')) + expect(runComments({ pattern: file, maxLines: 2, warn: true }).ok).toBe(true) + }) + + it('leaves JSDoc and context: blocks alone regardless of length', () => { + const file = write('exempt.ts', ['/**', ' * a', ' * b', ' * c', ' */', '// context: durable', '// more', 'const a = 1'].join('\n')) + expect(runComments({ pattern: file, maxLines: 1 }).ok).toBe(true) + }) +}) diff --git a/src/checks/comments.ts b/src/checks/comments.ts new file mode 100644 index 0000000..501d6b8 --- /dev/null +++ b/src/checks/comments.ts @@ -0,0 +1,121 @@ +import fs from 'node:fs' + +import { minimatch } from 'minimatch' + +import { DEFAULT_IGNORE, DEFAULT_PATTERN, findSourceFiles, resolvePattern } from '../analyze.ts' +import { findLongCommentBlocks } from '../comments.ts' +import { printCommentBlockReport } from '../report.ts' +import { color } from '../shared/color.ts' +import { scanFileComments } from '../shared/comment-scan.ts' +import { loadVerifyConfig } from '../shared/config.ts' +import { parseDiffAddedLines } from '../shared/diff.ts' +import { gitDiffAgainstBase } from '../shared/git.ts' +import type { CheckResult } from './types.ts' + +const DEFAULT_MAX_COMMENT_BLOCK_LINES = 2 + +const SCANNED_EXTENSIONS = ['.ts', '.tsx', '.cts', '.mts', '.js', '.jsx', '.mjs', '.cjs', '.yml', '.yaml'] + +// context: machine directives steer tooling, not humans, so a changed line carrying one is never a "comment" +// worth removing; `context:` is this project's durable-context escape hatch and is likewise exempt. +const MACHINE_DIRECTIVES = [ + 'oxlint-disable', + 'oxlint-enable', + '@ts-expect-error', + '@ts-ignore', + '@ts-nocheck', + 'eslint-disable', + 'eslint-enable', + 'prettier-ignore', + 'istanbul ignore', + 'v8 ignore', + 'c8 ignore', + '@vitest-environment', +] + +function isCommentExempt(text: string): boolean { + const lower = text.toLowerCase() + if (MACHINE_DIRECTIVES.some((d) => lower.includes(d))) return true + const stripped = text.replace(/^\s*(?:\/\/+|\/\*+|\*|#)\s*/, '') + return stripped.toLowerCase().startsWith('context:') +} + +function shouldScan(file: string, ignoreGlobs: readonly string[]): boolean { + if (!SCANNED_EXTENSIONS.some((ext) => file.endsWith(ext))) return false + if (ignoreGlobs.some((glob) => minimatch(file, glob))) return false + return fs.existsSync(file) +} + +function toSingleLine(text: string): string { + return text.replace(/\s+/g, ' ').trim() +} + +type NewComment = { file: string; line: number; text: string } + +// context: the --block-new-comments behaviour — flag any comment sitting on a changed line (vs HEAD, or the CI base). +function findCommentsOnChangedLines(ignoreGlobs: readonly string[]): NewComment[] { + const added = parseDiffAddedLines(gitDiffAgainstBase()) + const findings: NewComment[] = [] + for (const [file, lines] of added) { + if (!shouldScan(file, ignoreGlobs)) continue + for (const comment of scanFileComments(file)) { + if (!lines.has(comment.line)) continue + if (isCommentExempt(comment.text)) continue + findings.push({ file, line: comment.line, text: toSingleLine(comment.text) }) + } + } + findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line) + return findings +} + +function reportCommentsOnChangedLines(findings: readonly NewComment[]): void { + console.error(color.red('\nComments on changed lines:\n')) + for (const { file, line, text } of findings) console.error(` ${file}:${line} → ${text}`) + console.error(color.red(`\nTotal: ${findings.length} comment(s)`)) + console.error( + '\nWith --block-new-comments, every comment on a changed line fails this gate, whether you added or edited it. Remove them and let the code document itself.', + ) +} + +export type CommentsOptions = { + pattern?: string + ignore?: readonly string[] + maxLines?: number + pushback?: boolean + warn?: boolean + /** Also fail on any comment on a line changed against HEAD (machine directives / context: exempt). */ + blockNewComments?: boolean +} + +/** + * Native check: flag comment blocks longer than `maxLines` (JSDoc and `context:`-prefixed blocks exempt). + * With `blockNewComments`, additionally fail on any comment on a line changed against HEAD. + */ +export function runComments(opts: CommentsOptions = {}): CheckResult { + const maxLines = opts.maxLines ?? DEFAULT_MAX_COMMENT_BLOCK_LINES + const pattern = opts.pattern ?? DEFAULT_PATTERN + const files = findSourceFiles(resolvePattern(pattern), [...DEFAULT_IGNORE, ...(opts.ignore ?? [])]) + + const blocks = findLongCommentBlocks(files, maxLines) + let blocksOk = true + if (blocks.length === 0) { + console.log(color.green(`No comment block over ${maxLines} line(s)`)) + } else { + printCommentBlockReport(blocks, maxLines, { pushback: !!opts.pushback, warn: !!opts.warn }) + blocksOk = !!opts.warn + } + + let changedLinesOk = true + if (opts.blockNewComments) { + const ignoreGlobs = opts.ignore?.length ? opts.ignore : (loadVerifyConfig().comments?.ignore ?? []) + const findings = findCommentsOnChangedLines(ignoreGlobs) + if (findings.length === 0) { + console.log(color.green('No comments on changed lines.')) + } else { + reportCommentsOnChangedLines(findings) + changedLinesOk = false + } + } + + return { name: 'comments', ok: blocksOk && changedLinesOk } +} diff --git a/src/checks/complexity.ts b/src/checks/complexity.ts new file mode 100644 index 0000000..6444114 --- /dev/null +++ b/src/checks/complexity.ts @@ -0,0 +1,32 @@ +import { analyzeComplexity } from '../analyze.ts' +import { printFailure, printFileDetail, printMaintainabilityReport } from '../report.ts' +import { color } from '../shared/color.ts' +import type { CheckResult } from './types.ts' + +const DEFAULT_THRESHOLD = 50 + +export type ComplexityOptions = { + pattern?: string + ignore?: readonly string[] + threshold?: number +} + +/** Native maintainability-index check. A single matched file prints a per-metric breakdown instead of the gate. */ +export function runComplexity(opts: ComplexityOptions = {}): CheckResult { + const threshold = opts.threshold ?? DEFAULT_THRESHOLD + const analysis = analyzeComplexity({ pattern: opts.pattern, ignore: opts.ignore, threshold }) + + if (analysis.files.length === 0) { + console.log(color.yellow('complexity: no files matched — skipping')) + return { name: 'complexity', ok: true } + } + if (analysis.files.length === 1) { + printFileDetail(analysis.files[0] as string) + return { name: 'complexity', ok: true } + } + + printMaintainabilityReport(analysis.results, analysis.failing, threshold) + const ok = analysis.failing.length === 0 + if (!ok) printFailure(analysis.failing, threshold) + return { name: 'complexity', ok } +} diff --git a/src/checks/external.test.ts b/src/checks/external.test.ts new file mode 100644 index 0000000..d0491d0 --- /dev/null +++ b/src/checks/external.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest' + +import { externalFailureHint, selectCommand } from './external.ts' + +const fixable = { checkCommand: 'oxfmt --check .', fixCommand: 'oxfmt .' } +const notFixable = { checkCommand: 'tsc --noEmit' } + +describe('selectCommand', () => { + it('uses the fix command in fix mode when the check is fixable', () => { + expect(selectCommand(fixable, 'fix')).toBe('oxfmt .') + }) + + it('uses the check command in check mode', () => { + expect(selectCommand(fixable, 'check')).toBe('oxfmt --check .') + }) + + it('always uses the check command when the check has no fix command', () => { + expect(selectCommand(notFixable, 'fix')).toBe('tsc --noEmit') + expect(selectCommand(notFixable, 'check')).toBe('tsc --noEmit') + }) +}) + +describe('externalFailureHint', () => { + it('names the tool, the exact command that ran, and the docs link', () => { + const hint = externalFailureHint({ name: 'unused-code', bin: 'knip', docs: 'https://knip.dev' }, 'knip --no-progress') + expect(hint).toContain('unused-code') + expect(hint).toContain('knip') + expect(hint).toContain('knip --no-progress') + expect(hint).toContain('https://knip.dev') + }) + + it('still names the tool and command when no docs link is set', () => { + const hint = externalFailureHint({ name: 'circular-deps', bin: 'skott' }, 'skott src') + expect(hint).toContain('skott') + expect(hint).toContain('skott src') + expect(hint).not.toContain('undefined') + }) +}) diff --git a/src/checks/external.ts b/src/checks/external.ts new file mode 100644 index 0000000..8a6c1ca --- /dev/null +++ b/src/checks/external.ts @@ -0,0 +1,84 @@ +import fs from 'node:fs' +import path from 'node:path' + +import { color } from '../shared/color.ts' +import { resolveMode } from '../shared/mode.ts' +import { runCommand } from '../shared/spawn.ts' +import type { Check, CheckMode, CheckResult } from './types.ts' + +const BIN_EXTENSIONS = ['', '.cmd', '.ps1', '.exe'] + +/** True when a project-local binary is installed under node_modules/.bin (cross-platform). */ +function hasLocalBin(bin: string, cwd: string = process.cwd()): boolean { + const dir = path.join(cwd, 'node_modules', '.bin') + return BIN_EXTENSIONS.some((ext) => fs.existsSync(path.join(dir, bin + ext))) +} + +/** + * Put the project's node_modules/.bin on PATH so tools resolve however `verify` was invoked + * (npm script, npx, or directly) — npm only augments PATH when it runs a script itself. + */ +function envWithLocalBin(cwd: string = process.cwd()): Record { + const binDir = path.join(cwd, 'node_modules', '.bin') + const pathKey = Object.keys(process.env).find((k) => k.toLowerCase() === 'path') ?? 'PATH' + return { [pathKey]: `${binDir}${path.delimiter}${process.env[pathKey] ?? ''}` } +} + +export type ExternalCheckSpec = { + name: string + description: string + /** The node_modules/.bin executable that must be present for the check to run. */ + bin: string + /** Command run in check mode (report + fail, never rewrite). */ + checkCommand: string + /** Command run in fix mode. When omitted, the check is not fixable and always runs `checkCommand`. */ + fixCommand?: string + devDeps: string[] + recommended?: boolean + /** Docs / config reference for the underlying tool, surfaced when the check fails. */ + docs?: string + /** Extra guard beyond bin presence (e.g. require a tsconfig). */ + canRun?: () => boolean +} + +/** Pick the command for the run mode: the fix command only in fix mode and only when the check is fixable. */ +export function selectCommand(spec: Pick, mode: CheckMode): string { + return mode === 'fix' && spec.fixCommand ? spec.fixCommand : spec.checkCommand +} + +/** + * The line printed when an external check fails: names the tool (checks are named for their function, so the + * tool is otherwise hidden), the exact command that ran, and where to configure it — so an agent can set up + * the tool's config file (e.g. knip.json) without guessing. + */ +export function externalFailureHint(spec: Pick, command: string): string { + return `↳ ${spec.name} uses ${spec.bin}: ran \`${command}\`. Configure ${spec.bin}${spec.docs ? ` — ${spec.docs}` : ''}.` +} + +/** Build a Check that shells out to an external tool, skipping gracefully when the tool cannot run. */ +export function defineExternalCheck(spec: ExternalCheckSpec): Check { + return { + name: spec.name, + description: spec.description, + kind: 'external', + recommended: spec.recommended ?? false, + // Scaffold as a call into this CLI so fix-vs-check lives in one place, not the consumer's script. + scaffold: { script: `verifyx ${spec.name}`, devDeps: spec.devDeps }, + async runDefault(): Promise { + if (!hasLocalBin(spec.bin)) { + console.log(color.dim(`${spec.name}: ${spec.bin} not installed — skipping (add it with \`npx verifyx init\`)`)) + return { name: spec.name, ok: true, skipped: true } + } + if (spec.canRun && !spec.canRun()) { + console.log(color.dim(`${spec.name}: not applicable here — skipping`)) + return { name: spec.name, ok: true, skipped: true } + } + const command = selectCommand(spec, resolveMode()) + // quiet: buffer the tool's output and flush only on failure (streamed live under --verbose). + const code = await runCommand(command, { env: envWithLocalBin(), quiet: true }) + // Only on failure — passing runs stay silent to save tokens. + if (code !== 0) console.error(color.dim(externalFailureHint(spec, command))) + return { name: spec.name, ok: code === 0 } + }, + } +} diff --git a/src/checks/forbidden-strings.ts b/src/checks/forbidden-strings.ts new file mode 100644 index 0000000..441ddd7 --- /dev/null +++ b/src/checks/forbidden-strings.ts @@ -0,0 +1,65 @@ +import fs from 'node:fs' + +import { minimatch } from 'minimatch' + +import { color } from '../shared/color.ts' +import { type ForbiddenStringsRule, loadVerifyConfig } from '../shared/config.ts' +import type { CheckResult } from './types.ts' + +type ForbiddenStringViolation = { file: string; path: string; value: string } + +// Ported from https://github.com/staff0rd/assist verify/forbiddenStrings/findForbiddenStrings.ts +function resolveStringsAtPath(data: unknown, path: string): string[] { + let current: unknown = data + for (const segment of path.split('.')) { + if (current === null || typeof current !== 'object') return [] + current = (current as Record)[segment] + } + if (typeof current === 'string') return [current] + if (Array.isArray(current)) return current.filter((value): value is string => typeof value === 'string') + return [] +} + +function findRuleViolations(data: unknown, rule: ForbiddenStringsRule): ForbiddenStringViolation[] { + const violations: ForbiddenStringViolation[] = [] + for (const path of rule.paths) { + for (const value of resolveStringsAtPath(data, path)) { + if (minimatch(value, rule.disallowed)) violations.push({ file: rule.file, path, value }) + } + } + return violations +} + +function findForbiddenStrings(rules: readonly ForbiddenStringsRule[], readJson: (file: string) => unknown): ForbiddenStringViolation[] { + return rules.flatMap((rule) => findRuleViolations(readJson(rule.file), rule)) +} + +function readJsonFile(file: string): unknown { + try { + return JSON.parse(fs.readFileSync(file, 'utf-8')) + } catch { + return undefined + } +} + +export type ForbiddenStringsOptions = { rules?: readonly ForbiddenStringsRule[] } + +/** Fail when configured JSON values match a `disallowed` glob. Rules come from verify config. */ +export function runForbiddenStrings(opts: ForbiddenStringsOptions = {}): CheckResult { + const rules = opts.rules ?? loadVerifyConfig().forbiddenStrings ?? [] + if (rules.length === 0) { + console.log(color.dim('forbidden-strings: no rules configured — skipping')) + return { name: 'forbidden-strings', ok: true } + } + + const violations = findForbiddenStrings(rules, readJsonFile) + if (violations.length === 0) { + console.log(color.green('No forbidden strings found.')) + return { name: 'forbidden-strings', ok: true } + } + + console.error(color.red('Forbidden strings found:\n')) + for (const { file, path, value } of violations) console.error(` ${file} → ${path}: ${value}`) + console.error(color.red(`\nTotal: ${violations.length} forbidden string(s)`)) + return { name: 'forbidden-strings', ok: false } +} diff --git a/src/checks/hardcoded-colors.ts b/src/checks/hardcoded-colors.ts new file mode 100644 index 0000000..b470793 --- /dev/null +++ b/src/checks/hardcoded-colors.ts @@ -0,0 +1,56 @@ +import fs from 'node:fs' +import path from 'node:path' + +import { minimatch } from 'minimatch' + +import { color } from '../shared/color.ts' +import { loadVerifyConfig } from '../shared/config.ts' +import type { CheckResult } from './types.ts' + +const COLOR_PATTERN = /0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,8}/ +const SCANNED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.css', '.scss', '.sass', '.less', '.styl', '.vue', '.svelte', '.html'] + +function walk(dir: string, out: string[]): void { + if (!fs.existsSync(dir)) return + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name) + if (entry.isDirectory()) { + if (entry.name !== 'node_modules' && entry.name !== '.git') walk(full, out) + } else if (entry.isFile() && SCANNED_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) { + out.push(full) + } + } +} + +export type HardcodedColorsOptions = { root?: string; ignore?: readonly string[] } + +/** Fail on literal hex / 0x colours in source. Cross-platform (no `grep`); suggests the design-token path. */ +export function runHardcodedColors(opts: HardcodedColorsOptions = {}): CheckResult { + const config = loadVerifyConfig() + const root = opts.root ?? config.hardcodedColors?.root ?? 'src' + const ignoreGlobs = opts.ignore?.length ? opts.ignore : (config.hardcodedColors?.ignore ?? []) + + const files: string[] = [] + walk(root, files) + + const findings: Array<{ file: string; line: number; value: string }> = [] + for (const file of files) { + if (ignoreGlobs.some((glob) => minimatch(file, glob))) continue + const lines = fs.readFileSync(file, 'utf-8').split('\n') + for (let i = 0; i < lines.length; i++) { + const match = (lines[i] as string).match(COLOR_PATTERN) + if (match) findings.push({ file, line: i + 1, value: match[0] }) + } + } + + if (findings.length === 0) { + console.log(color.green('No hardcoded colors found.')) + return { name: 'hardcoded-colors', ok: true } + } + + console.error(color.red('Hardcoded colors found:\n')) + for (const { file, line, value } of findings) console.error(` ${file}:${line} → ${value}`) + console.error(color.red(`\nTotal: ${findings.length} hardcoded color(s)`)) + console.error('\nUse named tokens from your design system / color library instead of literal hex values.') + return { name: 'hardcoded-colors', ok: false } +} diff --git a/src/checks/registry.test.ts b/src/checks/registry.test.ts new file mode 100644 index 0000000..05a7e91 --- /dev/null +++ b/src/checks/registry.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest' + +import { CHECKS, getCheck, recommendedChecks } from './registry.ts' + +describe('check registry', () => { + it('includes the native and external checks', () => { + const names = CHECKS.map((c) => c.name) + expect(names).toEqual( + expect.arrayContaining([ + 'complexity', + 'comments', + 'hardcoded-colors', + 'forbidden-strings', + 'lint', + 'format', + 'check-types', + 'unused-code', + 'circular-deps', + 'duplicate-code', + ]), + ) + }) + + it('names checks for their function, not the underlying tool', () => { + expect(getCheck('knip')).toBeUndefined() + expect(getCheck('skott')).toBeUndefined() + expect(getCheck('jscpd')).toBeUndefined() + }) + + it('marks external tool checks with scaffold devDeps and a verifyx-CLI script', () => { + const unused = getCheck('unused-code') + expect(unused?.kind).toBe('external') + // The check is named for its function; the tool (knip) is only an install detail. + expect(unused?.scaffold.devDeps).toContain('knip') + expect(unused?.scaffold.script).toBe('verifyx unused-code') + }) + + it('scaffolds native checks back into the verifyx CLI', () => { + expect(getCheck('complexity')?.scaffold.script).toBe('verifyx complexity') + }) + + it('returns undefined for unknown checks', () => { + expect(getCheck('nope')).toBeUndefined() + }) + + it('recommends a subset for init preselection, all within the registry', () => { + const recommended = recommendedChecks() + expect(recommended.length).toBeGreaterThan(0) + expect(recommended.length).toBeLessThan(CHECKS.length) + for (const check of recommended) expect(CHECKS).toContain(check) + }) +}) diff --git a/src/checks/registry.ts b/src/checks/registry.ts new file mode 100644 index 0000000..a1bbd9b --- /dev/null +++ b/src/checks/registry.ts @@ -0,0 +1,96 @@ +import fs from 'node:fs' + +import { runComments } from './comments.ts' +import { runComplexity } from './complexity.ts' +import { defineExternalCheck } from './external.ts' +import { runForbiddenStrings } from './forbidden-strings.ts' +import { runHardcodedColors } from './hardcoded-colors.ts' +import type { Check, CheckResult } from './types.ts' + +function nativeCheck(name: string, description: string, recommended: boolean, run: () => CheckResult, script = `verifyx ${name}`): Check { + return { + name, + description, + kind: 'native', + recommended, + // Native checks scaffold as a call back into this CLI's own subcommand. + scaffold: { script }, + runDefault: async () => run(), + } +} + +// context: checks are named for their function, never the tool behind them (see each check's bin/devDeps). +export const CHECKS: Check[] = [ + nativeCheck('complexity', 'Maintainability-index gate (cyclomatic + Halstead + SLOC)', true, () => runComplexity()), + nativeCheck( + 'comments', + 'Flag long comment blocks (JSDoc / context: exempt); --block-new-comments also fails comments on changed lines', + true, + () => runComments({ pushback: true }), + 'verifyx comments --pushback', + ), + nativeCheck('hardcoded-colors', 'Fail on literal hex / 0x colour values in source', false, () => runHardcodedColors()), + nativeCheck('forbidden-strings', 'Fail on disallowed JSON config values (rules from verify config)', false, () => runForbiddenStrings()), + defineExternalCheck({ + name: 'lint', + description: 'Lint — auto-fixes locally, checks in CI', + bin: 'oxlint', + checkCommand: 'oxlint .', + fixCommand: 'oxlint --fix .', + devDeps: ['oxlint'], + recommended: true, + docs: 'https://oxc.rs/docs/guide/usage/linter.html', + }), + defineExternalCheck({ + name: 'format', + description: 'Formatting — writes locally, checks in CI', + bin: 'oxfmt', + checkCommand: 'oxfmt --check .', + fixCommand: 'oxfmt .', + devDeps: ['oxfmt'], + recommended: true, + docs: 'https://oxc.rs', + }), + defineExternalCheck({ + name: 'check-types', + description: 'TypeScript type check', + bin: 'tsc', + checkCommand: 'tsc --noEmit', + devDeps: ['typescript'], + canRun: () => fs.existsSync('tsconfig.json'), + recommended: true, + docs: 'https://www.typescriptlang.org/tsconfig', + }), + defineExternalCheck({ + name: 'unused-code', + description: 'Unused files, exports and dependencies', + bin: 'knip', + checkCommand: 'knip --no-progress --treat-config-hints-as-errors', + devDeps: ['knip'], + docs: 'https://knip.dev/reference/configuration', + }), + defineExternalCheck({ + name: 'circular-deps', + description: 'Circular dependency detection', + bin: 'skott', + checkCommand: 'skott --displayMode=raw --showCircularDependencies --exitCodeOnCircularDependencies=1', + devDeps: ['skott'], + docs: 'https://github.com/antoine-coulon/skott', + }), + defineExternalCheck({ + name: 'duplicate-code', + description: 'Copy-paste / duplicate-code detection', + bin: 'jscpd', + checkCommand: 'jscpd --format typescript,tsx --exit-code 1 --ignore "**/*.test.*" -r consoleFull src', + devDeps: ['jscpd'], + docs: 'https://github.com/kucherenko/jscpd/tree/master/apps/jscpd#config', + }), +] + +export function getCheck(name: string): Check | undefined { + return CHECKS.find((check) => check.name === name) +} + +export function recommendedChecks(): Check[] { + return CHECKS.filter((check) => check.recommended) +} diff --git a/src/checks/types.ts b/src/checks/types.ts new file mode 100644 index 0000000..35abe7f --- /dev/null +++ b/src/checks/types.ts @@ -0,0 +1,30 @@ +export type CheckKind = 'native' | 'external' + +/** Fixable checks auto-fix in `fix` mode and only report (failing on issues) in `check` mode. */ +export type CheckMode = 'fix' | 'check' + +export type CheckResult = { + name: string + ok: boolean + /** True when the check could not run because its underlying tool is not installed. */ + skipped?: boolean + durationMs?: number +} + +/** A single verification. Native checks run in-process; external checks shell out to a tool. */ +export type Check = { + name: string + description: string + kind: CheckKind + /** Whether `verifyx init` preselects this check as a recommended default. */ + recommended: boolean + /** Run the check with its default options and print its own report. Resolves to the outcome. */ + runDefault: () => Promise + /** How `verify init` wires this check into a consuming project. */ + scaffold: { + /** The npm script body written as `verify:`. */ + script: string + /** Extra devDependencies the check needs, installed on opt-in. */ + devDeps?: string[] + } +} diff --git a/src/cli-core.ts b/src/cli-core.ts deleted file mode 100644 index b554243..0000000 --- a/src/cli-core.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { analyzeComplexity, DEFAULT_IGNORE, DEFAULT_PATTERN, findLongCommentBlocks, findSourceFiles, resolvePattern } from './index.ts' -import { color, printCommentBlockReport, printFailure, printFileDetail, printMaintainabilityReport } from './report.ts' - -export type CliArgs = { - pattern?: string - ignore: string[] - threshold?: number - maxCommentBlockLines?: number - commentBlockPushback: boolean - commentBlockWarn: boolean -} - -export function parseArgs(argv: readonly string[]): CliArgs { - const ignore: string[] = [] - let pattern: string | undefined - let threshold: number | undefined - let maxCommentBlockLines: number | undefined - let commentBlockPushback = false - let commentBlockWarn = false - for (let i = 0; i < argv.length; i++) { - const arg = argv[i] - if (arg === '--threshold') { - threshold = Number(argv[++i]) - } else if (arg === '--ignore') { - ignore.push(argv[++i] as string) - } else if (arg === '--max-comment-block-lines') { - maxCommentBlockLines = Number(argv[++i]) - } else if (arg === '--comment-block-pushback') { - commentBlockPushback = true - } else if (arg === '--comment-block-warn') { - commentBlockWarn = true - } else { - pattern = arg - } - } - return { pattern, ignore, threshold, maxCommentBlockLines, commentBlockPushback, commentBlockWarn } -} - -/** Run the opt-in comment-block check, report any violations, and return whether it should fail the run. */ -function checkCommentBlocks(files: readonly string[], args: CliArgs): boolean { - if (args.maxCommentBlockLines === undefined) return false - const violations = findLongCommentBlocks(files, args.maxCommentBlockLines) - if (violations.length === 0) return false - printCommentBlockReport(violations, args.maxCommentBlockLines, { pushback: args.commentBlockPushback, warn: args.commentBlockWarn }) - return !args.commentBlockWarn -} - -/** Run the CLI against the given argv (already sliced of `node` + script). Returns the process exit code. */ -export function run(argv: readonly string[]): number { - const args = parseArgs(argv) - const { pattern, ignore, threshold } = args - const resolvedPattern = pattern ?? DEFAULT_PATTERN - const files = findSourceFiles(resolvePattern(resolvedPattern), [...DEFAULT_IGNORE, ...ignore]) - - if (files.length === 0) { - console.log(color.yellow('No files found matching pattern')) - return threshold !== undefined ? 1 : 0 - } - if (files.length === 1) { - printFileDetail(files[0] as string) - return checkCommentBlocks(files, args) ? 1 : 0 - } - - const { results, failing } = analyzeComplexity({ pattern: resolvedPattern, ignore, threshold }) - printMaintainabilityReport(results, failing, threshold) - const thresholdFailed = threshold !== undefined && failing.length > 0 - if (thresholdFailed) printFailure(failing, threshold) - const commentFailed = checkCommentBlocks(files, args) - return thresholdFailed || commentFailed ? 1 : 0 -} diff --git a/src/cli.test.ts b/src/cli.test.ts deleted file mode 100644 index 326031a..0000000 --- a/src/cli.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { type CliArgs, parseArgs } from './cli-core.ts' - -const DEFAULTS: CliArgs = { - pattern: undefined, - ignore: [], - threshold: undefined, - maxCommentBlockLines: undefined, - commentBlockPushback: false, - commentBlockWarn: false, -} - -describe('parseArgs', () => { - it('parses a positional pattern', () => { - expect(parseArgs(['src/**/*.ts'])).toEqual({ ...DEFAULTS, pattern: 'src/**/*.ts' }) - }) - - it('parses --threshold as a number', () => { - expect(parseArgs(['--threshold', '50'])).toEqual({ ...DEFAULTS, threshold: 50 }) - }) - - it('accumulates repeated --ignore flags', () => { - const { ignore } = parseArgs(['--ignore', '**/a.ts', '--ignore', '**/b.ts']) - expect(ignore).toEqual(['**/a.ts', '**/b.ts']) - }) - - it('parses a full combination of args', () => { - expect(parseArgs(['src/**/*.ts', '--threshold', '60', '--ignore', '**/gen.ts'])).toEqual({ - ...DEFAULTS, - pattern: 'src/**/*.ts', - ignore: ['**/gen.ts'], - threshold: 60, - }) - }) - - it('keeps the last positional when several are given', () => { - expect(parseArgs(['first', 'second']).pattern).toBe('second') - }) - - it('parses --max-comment-block-lines as a number', () => { - expect(parseArgs(['--max-comment-block-lines', '2']).maxCommentBlockLines).toBe(2) - }) - - it('parses the comment-block boolean flags', () => { - const { commentBlockPushback, commentBlockWarn } = parseArgs(['--comment-block-pushback', '--comment-block-warn']) - expect(commentBlockPushback).toBe(true) - expect(commentBlockWarn).toBe(true) - }) - - it('parses comment-block flags alongside the existing flags', () => { - expect(parseArgs(['src/**/*.ts', '--threshold', '50', '--max-comment-block-lines', '3', '--comment-block-pushback'])).toEqual({ - ...DEFAULTS, - pattern: 'src/**/*.ts', - threshold: 50, - maxCommentBlockLines: 3, - commentBlockPushback: true, - }) - }) -}) diff --git a/src/cli.ts b/src/cli.ts index a2c319e..1294746 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,4 +1,59 @@ #!/usr/bin/env node -import { run } from './cli-core.ts' +import { createRequire } from 'node:module' -process.exit(run(process.argv.slice(2))) +import { Command } from 'commander' + +import { registerChecks } from './commands/registerChecks.ts' +import { registerInit } from './commands/registerInit.ts' +import { registerList } from './commands/registerList.ts' +import { registerUpgradeDocs } from './commands/registerUpgradeDocs.ts' +import { orchestrate } from './orchestrator/run.ts' +import { runAll } from './orchestrator/runAll.ts' +import { configureMode } from './shared/mode.ts' + +const require = createRequire(import.meta.url) +const pkg = require('../package.json') as { version: string } + +type RunOptions = { measure?: boolean; verbose?: boolean; check?: boolean; fix?: boolean; tests?: boolean } + +/** The options shared by the default run and `verifyx all`. */ +function withRunOptions(command: Command): Command { + return command + .option('--measure', "print a summary table of each verification's status and duration") + .option('--verbose', 'stream all output instead of suppressing passing runs') + .option('--check', 'check only — never auto-fix (the default under CI)') + .option('--fix', 'auto-fix where possible (the default locally)') + .option('--no-tests', 'skip the automatic tests step (verify:test / test locally, test:ci on CI)') +} + +const program = new Command() + +withRunOptions( + program + .name('verifyx') + .description( + 'A growing collection of code verifications that give AI coding agents back-pressure against writing hard-to-maintain code.', + ) + .version(pkg.version), +).action(async (opts: RunOptions) => { + process.exitCode = await orchestrate(opts) +}) + +withRunOptions( + program.command('all').description('Run every built-in check (verify: scripts override the matching built-in)'), +).action(async (_opts: RunOptions, command: Command) => { + // Run options live on the root command (commander treats them as global), so merge globals to catch flags after `all`. + const opts = command.optsWithGlobals() as RunOptions + configureMode(opts) + process.exitCode = await runAll({ measure: opts.measure, tests: opts.tests, verbose: opts.verbose }) +}) + +registerChecks(program) +registerList(program) +registerInit(program) +registerUpgradeDocs(program) + +program.parseAsync().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/src/commands/registerChecks.ts b/src/commands/registerChecks.ts new file mode 100644 index 0000000..791e699 --- /dev/null +++ b/src/commands/registerChecks.ts @@ -0,0 +1,82 @@ +import type { Command } from 'commander' + +import { runComments } from '../checks/comments.ts' +import { runComplexity } from '../checks/complexity.ts' +import { runForbiddenStrings } from '../checks/forbidden-strings.ts' +import { runHardcodedColors } from '../checks/hardcoded-colors.ts' +import { CHECKS } from '../checks/registry.ts' + +function finish(ok: boolean): void { + process.exitCode = ok ? 0 : 1 +} + +function collect(value: string, previous: string[]): string[] { + return [...previous, value] +} + +/** Register a directly-invocable subcommand for every built-in check (`verifyx complexity`, `verifyx knip`, …). */ +export function registerChecks(program: Command): void { + program + .command('complexity') + .description('Maintainability-index gate') + .argument('[pattern]', 'glob, directory, or file to analyse') + .option('--threshold ', 'fail files whose minimum MI is below this', Number) + .option('--ignore ', 'ignore glob (repeatable)', collect, []) + .action((pattern: string | undefined, opts: { threshold?: number; ignore: string[] }) => { + finish(runComplexity({ pattern, threshold: opts.threshold, ignore: opts.ignore }).ok) + }) + + program + .command('comments') + .description('Flag long comment blocks (JSDoc / context: exempt); --block-new-comments also fails comments on changed lines') + .argument('[pattern]', 'glob, directory, or file to scan') + .option('--max-lines ', 'maximum comment-block length', Number) + .option('--pushback', 'add AI back-pressure framing to the failure message') + .option('--warn', 'report without failing the run') + .option('--block-new-comments', 'also fail on any comment on a line changed against HEAD') + .option('--ignore ', 'ignore glob (repeatable)', collect, []) + .action( + ( + pattern: string | undefined, + opts: { maxLines?: number; pushback?: boolean; warn?: boolean; blockNewComments?: boolean; ignore: string[] }, + ) => { + finish( + runComments({ + pattern, + maxLines: opts.maxLines, + pushback: opts.pushback, + warn: opts.warn, + blockNewComments: opts.blockNewComments, + ignore: opts.ignore, + }).ok, + ) + }, + ) + + program + .command('hardcoded-colors') + .description('Fail on literal hex / 0x colour values in source') + .option('--root ', 'directory to scan') + .option('--ignore ', 'ignore glob (repeatable)', collect, []) + .action((opts: { root?: string; ignore: string[] }) => { + finish(runHardcodedColors({ root: opts.root, ignore: opts.ignore }).ok) + }) + + program + .command('forbidden-strings') + .description('Fail on disallowed JSON config values (rules from verify config)') + .action(() => { + finish(runForbiddenStrings().ok) + }) + + // Mode flows via the VERIFY_MODE env / CI, not per-subcommand flags (which collide with the root's --check). + for (const check of CHECKS.filter((c) => c.kind === 'external')) { + program + .command(check.name) + .description(check.description) + .action(async () => { + const result = await check.runDefault() + finish(result.ok) + }) + } +} diff --git a/src/commands/registerInit.ts b/src/commands/registerInit.ts new file mode 100644 index 0000000..d962c9c --- /dev/null +++ b/src/commands/registerInit.ts @@ -0,0 +1,102 @@ +import type { Command } from 'commander' +import enquirer from 'enquirer' + +import { CHECKS, recommendedChecks } from '../checks/registry.ts' +import type { AgentTarget } from '../scaffold/agentFiles.ts' +import { applyInit, type InitResult } from '../scaffold/init.ts' +import { ACTION_MARK } from '../scaffold/writeManaged.ts' +import { color } from '../shared/color.ts' +import { runCommand } from '../shared/spawn.ts' + +type Choice = { name: string; message: string; enabled?: boolean } + +function collect(value: string, previous: string[]): string[] { + return [...previous, value] +} + +async function ask(type: 'select' | 'multiselect', message: string, choices: Choice[]): Promise { + const response = (await enquirer.prompt({ type, name: 'selected', message, choices })) as { selected: T } + return response.selected +} + +type InitCliOptions = { + defaultsOnly?: boolean + yes?: boolean + select: string[] + claude?: boolean + agents?: boolean +} + +type Selections = { checks: string[]; targets: AgentTarget[]; defaultsOnly: boolean } + +async function resolveSelections(opts: InitCliOptions): Promise { + const nonInteractive = !!opts.yes || !process.stdin.isTTY + if (nonInteractive) { + const targets: AgentTarget[] = [] + if (opts.claude !== false) targets.push('claude') + if (opts.agents) targets.push('agents') + return { + checks: opts.select.length > 0 ? opts.select : recommendedChecks().map((c) => c.name), + targets, + defaultsOnly: !!opts.defaultsOnly, + } + } + + // context: an up-front choice — run everything with defaults (verifyx all, no verify:* scripts), or hand-pick checks. + const mode = await ask('select', 'How should verify run?', [ + { name: 'defaults', message: 'Run all built-in checks (verifyx all) with default options', enabled: true }, + { name: 'pick', message: 'Pick specific checks to wire up as verify:* scripts' }, + ]) + const defaultsOnly = mode === 'defaults' + + // Defaults-only still passes every check so applyInit installs all their devDeps; hand-picking narrows the list. + const checks = defaultsOnly + ? CHECKS.map((c) => c.name) + : await ask( + 'multiselect', + 'Select checks to wire up', + CHECKS.map((c) => ({ name: c.name, message: `${c.name} — ${c.description}`, enabled: c.recommended })), + ) + const targets = await ask('multiselect', 'Select agent targets', [ + { name: 'claude', message: 'Claude (.claude/skills + CLAUDE.md)', enabled: true }, + { name: 'agents', message: 'Other agents (.agent-skills + AGENTS.md)', enabled: false }, + ]) + return { checks, targets, defaultsOnly } +} + +function report(result: InitResult, defaultsOnly: boolean): void { + if (defaultsOnly) { + console.log(color.dim('\nDefaults-only: no verify:* scripts written — the `verify` script runs `verifyx all` (every built-in).')) + } + console.log(color.green(`\nScripts added: ${result.addedScripts.join(', ') || '(none new)'}`)) + for (const file of result.agentFiles) { + if (file.action === 'unchanged') continue + console.log(` ${ACTION_MARK[file.action]} ${file.path} (${file.action})`) + } + console.log(color.dim('\nRun `npm run verify` (or `npx verifyx`) to run your verifications.')) +} + +/** `verifyx init` — interactively scaffold checks + agent files into the current project. */ +export function registerInit(program: Command): void { + program + .command('init') + .description('Scaffold verifications and agent commands into this project') + .option('--defaults-only', 'do not write verify:* scripts; rely on `verifyx` built-in defaults') + .option('--yes', 'non-interactive: use flag selections (or defaults) without prompting') + .option('--select ', 'preselect a check by name (repeatable, non-interactive)', collect, []) + .option('--no-claude', 'do not write .claude/ files (non-interactive)') + .option('--agents', 'also write .agent-skills/ files (non-interactive)') + .action(async (opts: InitCliOptions) => { + const cwd = process.cwd() + const { checks, targets, defaultsOnly } = await resolveSelections(opts) + + const result = applyInit({ cwd, checks, targets, defaultsOnly }) + + if (result.devDeps.length > 0) { + console.log(`Installing ${result.devDeps.length} devDependenc(ies): ${result.devDeps.join(', ')}`) + const code = await runCommand(`npm install --save-dev ${result.devDeps.join(' ')}`, { cwd }) + if (code !== 0) console.error(color.yellow('npm install failed — install those devDependencies manually.')) + } + report(result, defaultsOnly) + }) +} diff --git a/src/commands/registerList.ts b/src/commands/registerList.ts new file mode 100644 index 0000000..7f120c2 --- /dev/null +++ b/src/commands/registerList.ts @@ -0,0 +1,18 @@ +import type { Command } from 'commander' + +import { CHECKS } from '../checks/registry.ts' +import { color } from '../shared/color.ts' + +/** `verifyx list` — show every built-in check, its kind, and whether it runs in the default set. */ +export function registerList(program: Command): void { + program + .command('list') + .description('List all built-in checks') + .action(() => { + console.log(color.heading('Built-in checks')) + for (const check of CHECKS) { + const tags = color.dim(`(${check.kind}${check.recommended ? ', recommended' : ''})`) + console.log(` ${color.cyan(check.name.padEnd(18))} ${tags} ${check.description}`) + } + }) +} diff --git a/src/commands/registerUpgradeDocs.ts b/src/commands/registerUpgradeDocs.ts new file mode 100644 index 0000000..5ea16ba --- /dev/null +++ b/src/commands/registerUpgradeDocs.ts @@ -0,0 +1,26 @@ +import type { Command } from 'commander' + +import { type AgentTarget, writeAgentFiles } from '../scaffold/agentFiles.ts' +import { ACTION_MARK, summarise } from '../scaffold/writeManaged.ts' + +/** `verifyx upgrade-docs` — refresh the verify skill and append the pointer to CLAUDE.md / AGENTS.md. */ +export function registerUpgradeDocs(program: Command): void { + program + .command('upgrade-docs') + .description('Create/refresh the verify skill and the pointer in CLAUDE.md / AGENTS.md') + .option('--no-claude', 'skip .claude/skills and CLAUDE.md') + .option('--no-agents', 'skip .agent-skills and AGENTS.md') + .action((opts: { claude?: boolean; agents?: boolean }) => { + const targets: AgentTarget[] = [] + if (opts.claude !== false) targets.push('claude') + if (opts.agents !== false) targets.push('agents') + + const results = writeAgentFiles(process.cwd(), targets) + for (const result of results) { + if (result.action === 'unchanged') continue + console.log(` ${ACTION_MARK[result.action]} ${result.path} (${result.action})`) + } + const summary = summarise(results) + console.log(`\n${summary.created} created, ${summary.appended} appended, ${summary.updated} updated, ${summary.unchanged} unchanged.`) + }) +} diff --git a/src/index.ts b/src/index.ts index 8b603c4..6f47bf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,12 @@ export { resolvePattern, scoreFiles, } from './analyze.ts' +export { type CommentsOptions, runComments } from './checks/comments.ts' +export { runComplexity } from './checks/complexity.ts' +export { runForbiddenStrings } from './checks/forbidden-strings.ts' +export { runHardcodedColors } from './checks/hardcoded-colors.ts' +export { CHECKS, getCheck, recommendedChecks } from './checks/registry.ts' +export type { Check, CheckKind, CheckMode, CheckResult } from './checks/types.ts' export { type CommentBlockViolation, findLongCommentBlocks } from './comments.ts' export { type FunctionCallback, forEachFunction } from './functions.ts' export { @@ -18,3 +24,7 @@ export { countSloc, type HalsteadMetrics, } from './metrics.ts' +export { orchestrate } from './orchestrator/run.ts' +export { runAll } from './orchestrator/runAll.ts' +export { applyInit, type InitOptions, type InitResult } from './scaffold/init.ts' +export { type ForbiddenStringsRule, loadVerifyConfig, type VerifyConfig } from './shared/config.ts' diff --git a/src/orchestrator/measure.ts b/src/orchestrator/measure.ts new file mode 100644 index 0000000..c4df0a3 --- /dev/null +++ b/src/orchestrator/measure.ts @@ -0,0 +1,35 @@ +// Ported from https://github.com/staff0rd/assist verify/run/printMeasureTable.ts +export type MeasureRecord = { + script: string + code: number + durationMs: number +} + +function formatDuration(ms: number): string { + if (ms < 1000) return `${ms}ms` + return `${(ms / 1000).toFixed(1)}s` +} + +/** Print a status/duration summary table for a set of runs, sorted slowest-first, with a TOTAL row. */ +export function printMeasureTable(records: readonly MeasureRecord[], totalMs: number): void { + const rows = [...records] + .sort((a, b) => b.durationMs - a.durationMs) + .map((record) => ({ + status: record.code === 0 ? '✓' : '✗', + name: record.script, + duration: formatDuration(record.durationMs), + })) + + const nameWidth = Math.max('Command'.length, 'TOTAL'.length, ...rows.map((row) => row.name.length)) + const durationWidth = Math.max('Duration'.length, formatDuration(totalMs).length, ...rows.map((row) => row.duration.length)) + + const line = (status: string, name: string, duration: string): string => + ` ${status.padEnd(6)} ${name.padEnd(nameWidth)} ${duration.padStart(durationWidth)}` + const separator = ` ${' '.repeat(6)} ${'─'.repeat(nameWidth)} ${'─'.repeat(durationWidth)}` + + console.log() + console.log(line('Status', 'Command', 'Duration')) + for (const row of rows) console.log(line(row.status, row.name, row.duration)) + console.log(separator) + console.log(line('', 'TOTAL', formatDuration(totalMs))) +} diff --git a/src/orchestrator/report.test.ts b/src/orchestrator/report.test.ts new file mode 100644 index 0000000..14a895c --- /dev/null +++ b/src/orchestrator/report.test.ts @@ -0,0 +1,68 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { setVerbose } from '../shared/spawn.ts' +import { chatty, reportOutcomes } from './report.ts' + +let log: ReturnType +let err: ReturnType + +beforeEach(() => { + log = vi.spyOn(console, 'log').mockImplementation(() => {}) + err = vi.spyOn(console, 'error').mockImplementation(() => {}) +}) +afterEach(() => { + log.mockRestore() + err.mockRestore() + setVerbose(false) + delete process.env.CI + delete process.env.VERIFY_MODE +}) + +describe('chatty', () => { + it('is quiet by default (a clean run prints nothing)', () => { + setVerbose(false) + expect(chatty()).toBe(false) + }) + + it('is loud only when verbose is set (not for --measure)', () => { + setVerbose(true) + expect(chatty()).toBe(true) + }) +}) + +describe('reportOutcomes', () => { + const passing = [ + { name: 'lint', ok: true }, + { name: 'complexity', ok: true }, + ] + + it('is silent and returns 0 on success when not chatty', () => { + const code = reportOutcomes(passing, 'verification', false) + expect(code).toBe(0) + expect(log).not.toHaveBeenCalled() + expect(err).not.toHaveBeenCalled() + }) + + it('prints the pass line and returns 0 on success when chatty', () => { + const code = reportOutcomes(passing, 'verification', true) + expect(code).toBe(0) + expect(log).toHaveBeenCalledTimes(1) + expect(log.mock.calls[0]?.[0]).toContain('All 2 verification(s) passed') + }) + + it('always reports failures (and returns 1) regardless of chatty', () => { + const outcomes = [ + { name: 'lint', ok: true }, + { name: 'unused-code', ok: false }, + { name: 'circular-deps', ok: false }, + ] + const code = reportOutcomes(outcomes, 'verification', false) + expect(code).toBe(1) + expect(err).toHaveBeenCalledTimes(1) + const message = String(err.mock.calls[0]?.[0]) + expect(message).toContain('2 verification(s) failed') + expect(message).toContain('unused-code') + expect(message).toContain('circular-deps') + expect(log).not.toHaveBeenCalled() + }) +}) diff --git a/src/orchestrator/report.ts b/src/orchestrator/report.ts new file mode 100644 index 0000000..ced64a9 --- /dev/null +++ b/src/orchestrator/report.ts @@ -0,0 +1,26 @@ +import { color } from '../shared/color.ts' +import { isVerbose } from '../shared/spawn.ts' + +/** + * Whether to print the preamble + per-check output + success footer. A clean run is otherwise silent (just + * exit 0) to save tokens; that chatter is shown only under `--verbose`. `--measure` prints just its table. + */ +export function chatty(): boolean { + return isVerbose() +} + +export type RunOutcome = { name: string; ok: boolean } + +/** + * Report a batch of outcomes: failures are always printed; the "all passed" line only when `showPass`. + * Returns the process exit code (1 if anything failed, else 0). + */ +export function reportOutcomes(outcomes: readonly RunOutcome[], noun: string, showPass: boolean): number { + const failed = outcomes.filter((outcome) => !outcome.ok) + if (failed.length > 0) { + console.error(color.red(`\n${failed.length} ${noun}(s) failed: ${failed.map((f) => f.name).join(', ')}`)) + return 1 + } + if (showPass) console.log(color.green(`\nAll ${outcomes.length} ${noun}(s) passed`)) + return 0 +} diff --git a/src/orchestrator/resolveEntries.test.ts b/src/orchestrator/resolveEntries.test.ts new file mode 100644 index 0000000..d5c503c --- /dev/null +++ b/src/orchestrator/resolveEntries.test.ts @@ -0,0 +1,97 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { entryCheckName, resolveEntries, resolveOverride, selectEntries, type VerifyEntry } from './resolveEntries.ts' + +function entry(name: string): VerifyEntry { + return { name, command: `npm run ${name}`, cwd: '.' } +} + +let dir: string + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-entries-')) +}) +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }) +}) + +describe('resolveEntries', () => { + it('collects only verify:* scripts as npm run entries', () => { + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ scripts: { 'verify:a': 'x', build: 'y', 'verify:b': 'z' } })) + const entries = resolveEntries(dir) + expect(entries.map((e) => e.name).sort()).toEqual(['verify:a', 'verify:b']) + expect(entries.find((e) => e.name === 'verify:a')?.command).toBe('npm run verify:a') + }) + + it('returns [] when there are no verify:* scripts', () => { + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ scripts: { build: 'x' } })) + expect(resolveEntries(dir)).toEqual([]) + }) +}) + +describe('entryCheckName', () => { + it('strips the verify: prefix', () => { + expect(entryCheckName('verify:lint')).toBe('lint') + }) + it('strips a trailing :fix so a variant maps to its base check', () => { + expect(entryCheckName('verify:lint:fix')).toBe('lint') + expect(entryCheckName('verify:comments:fix')).toBe('comments') + }) +}) + +describe('selectEntries (bare verifyx: verify: / verify::fix pairs)', () => { + it('runs the base script in both modes when there is no :fix variant', () => { + const entries = [entry('verify:lint')] + expect(selectEntries(entries, 'check').map((e) => e.name)).toEqual(['verify:lint']) + expect(selectEntries(entries, 'fix').map((e) => e.name)).toEqual(['verify:lint']) + }) + + it('prefers the :fix variant in fix mode and the base in check mode', () => { + const entries = [entry('verify:lint'), entry('verify:lint:fix')] + expect(selectEntries(entries, 'fix').map((e) => e.name)).toEqual(['verify:lint:fix']) + expect(selectEntries(entries, 'check').map((e) => e.name)).toEqual(['verify:lint']) + }) + + it('never runs both the base and the :fix variant of the same check', () => { + const entries = [entry('verify:lint'), entry('verify:lint:fix')] + expect(selectEntries(entries, 'fix')).toHaveLength(1) + expect(selectEntries(entries, 'check')).toHaveLength(1) + }) + + it('uses a lone :fix variant in fix mode but skips it in check mode', () => { + const entries = [entry('verify:lint:fix')] + expect(selectEntries(entries, 'fix').map((e) => e.name)).toEqual(['verify:lint:fix']) + expect(selectEntries(entries, 'check')).toEqual([]) + }) + + it('keeps distinct checks independent', () => { + const entries = [entry('verify:lint'), entry('verify:lint:fix'), entry('verify:complexity')] + expect( + selectEntries(entries, 'fix') + .map((e) => e.name) + .sort(), + ).toEqual(['verify:complexity', 'verify:lint:fix']) + }) +}) + +describe('resolveOverride (verifyx all per-check override)', () => { + const entries = [entry('verify:lint'), entry('verify:lint:fix'), entry('verify:complexity')] + + it('returns the :fix variant in fix mode, the base in check mode', () => { + expect(resolveOverride(entries, 'lint', 'fix')?.name).toBe('verify:lint:fix') + expect(resolveOverride(entries, 'lint', 'check')?.name).toBe('verify:lint') + }) + + it('falls back to the base when there is no :fix variant', () => { + expect(resolveOverride(entries, 'complexity', 'fix')?.name).toBe('verify:complexity') + }) + + it('returns undefined when no override is defined (built-in is used)', () => { + expect(resolveOverride(entries, 'knip', 'fix')).toBeUndefined() + expect(resolveOverride([entry('verify:lint:fix')], 'lint', 'check')).toBeUndefined() + }) +}) diff --git a/src/orchestrator/resolveEntries.ts b/src/orchestrator/resolveEntries.ts new file mode 100644 index 0000000..8e60e79 --- /dev/null +++ b/src/orchestrator/resolveEntries.ts @@ -0,0 +1,83 @@ +import fs from 'node:fs' +import path from 'node:path' + +import type { CheckMode } from '../checks/types.ts' + +export type VerifyEntry = { + name: string + command: string + cwd: string +} + +const VERIFY_PREFIX = 'verify:' +const FIX_SUFFIX = ':fix' + +/** The check a `verify:*` script targets, ignoring a trailing `:fix`. `verify:lint` and `verify:lint:fix` → `lint`. */ +export function entryCheckName(entryName: string): string { + const withoutPrefix = entryName.startsWith(VERIFY_PREFIX) ? entryName.slice(VERIFY_PREFIX.length) : entryName + return withoutPrefix.endsWith(FIX_SUFFIX) ? withoutPrefix.slice(0, -FIX_SUFFIX.length) : withoutPrefix +} + +/** Walk up from `startDir` to the nearest package.json. */ +function findPackageJson(startDir: string): string | null { + let dir = path.resolve(startDir) + for (;;) { + const candidate = path.join(dir, 'package.json') + if (fs.existsSync(candidate)) return candidate + const parent = path.dirname(dir) + if (parent === dir) return null + dir = parent + } +} + +type PackageJson = { scripts?: Record } + +/** The nearest package.json's scripts + its directory, or null when none is found / it can't be parsed. */ +export function loadPackageScripts(cwd: string = process.cwd()): { scripts: Record; dir: string } | null { + const pkgPath = findPackageJson(cwd) + if (!pkgPath) return null + try { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as PackageJson + return { scripts: pkg.scripts ?? {}, dir: path.dirname(pkgPath) } + } catch { + return null + } +} + +/** Collect the project's `verify:*` npm scripts as parallelisable entries, nearest package.json wins. */ +export function resolveEntries(cwd: string = process.cwd()): VerifyEntry[] { + const loaded = loadPackageScripts(cwd) + if (!loaded) return [] + return Object.keys(loaded.scripts) + .filter((name) => name.startsWith(VERIFY_PREFIX)) + .map((name) => ({ name, command: `npm run ${name}`, cwd: loaded.dir })) +} + +/** + * Collapse `verify:` / `verify::fix` pairs to one entry per check for the run mode: fix mode + * prefers the `:fix` variant (falling back to the base), check mode uses the base only. This is what bare + * `verifyx` runs. + */ +export function selectEntries(entries: readonly VerifyEntry[], mode: CheckMode): VerifyEntry[] { + const byCheck = new Map() + for (const entry of entries) { + const key = entryCheckName(entry.name) + const group = byCheck.get(key) ?? {} + if (entry.name.endsWith(FIX_SUFFIX)) group.fix = entry + else group.base = entry + byCheck.set(key, group) + } + const selected: VerifyEntry[] = [] + for (const { base, fix } of byCheck.values()) { + const chosen = mode === 'fix' ? (fix ?? base) : base + if (chosen) selected.push(chosen) + } + return selected +} + +/** The override script for a built-in `` under `verifyx all`: the `:fix` variant in fix mode, else the base. */ +export function resolveOverride(entries: readonly VerifyEntry[], checkName: string, mode: CheckMode): VerifyEntry | undefined { + const fix = entries.find((entry) => entry.name === `${VERIFY_PREFIX}${checkName}${FIX_SUFFIX}`) + const base = entries.find((entry) => entry.name === `${VERIFY_PREFIX}${checkName}`) + return mode === 'fix' ? (fix ?? base) : base +} diff --git a/src/orchestrator/run.ts b/src/orchestrator/run.ts new file mode 100644 index 0000000..117ddcf --- /dev/null +++ b/src/orchestrator/run.ts @@ -0,0 +1,57 @@ +import { configureMode, resolveMode } from '../shared/mode.ts' +import { runCommand, setVerbose } from '../shared/spawn.ts' +import { type MeasureRecord, printMeasureTable } from './measure.ts' +import { chatty, reportOutcomes } from './report.ts' +import { entryCheckName, resolveEntries, selectEntries, type VerifyEntry } from './resolveEntries.ts' +import { resolveTestEntry, TEST_CHECK_NAME } from './tests.ts' + +export type OrchestrateOptions = { + measure?: boolean + verbose?: boolean + check?: boolean + fix?: boolean + /** When false, skip the automatic tests step (`--no-tests`). */ + tests?: boolean +} + +async function runEntry(entry: VerifyEntry): Promise { + const startTime = Date.now() + const code = await runCommand(entry.command, { cwd: entry.cwd, quiet: true }) + return { script: entry.name, code, durationMs: Date.now() - startTime } +} + +/** + * The default `verifyx` action. Convention: run the project's own `verify:*` scripts in parallel (output + * buffered, flushed only on failure). A clean run is silent — use `--verbose` or `--measure` for detail. + * With no `verify:*` scripts, nothing runs — use `verifyx all` to run every built-in check. + */ +export async function orchestrate(opts: OrchestrateOptions = {}): Promise { + setVerbose(!!opts.verbose) + // Propagate an explicit --check/--fix via VERIFY_MODE so it reaches spawned verify:* scripts too. + configureMode(opts) + + // Collapse verify:/:fix pairs per mode; the tests step owns the `test` check, so it's dropped and re-added below. + const gate = selectEntries(resolveEntries(), resolveMode()).filter((entry) => entryCheckName(entry.name) !== TEST_CHECK_NAME) + const testEntry = resolveTestEntry({ noTests: opts.tests === false }) + const entries = testEntry ? [...gate, testEntry] : gate + + if (entries.length === 0) { + console.log('No verify:* scripts defined — nothing to run. Add verify:* scripts, or run `verifyx all` to run every built-in check.') + return 0 + } + + const loud = chatty() + if (loud) { + console.log(`Running ${entries.length} verify script(s) in parallel:`) + for (const entry of entries) console.log(` - ${entry.name}`) + } + + const startTime = Date.now() + const records = await Promise.all(entries.map(runEntry)) + if (opts.measure) printMeasureTable(records, Date.now() - startTime) + return reportOutcomes( + records.map((r) => ({ name: r.script, ok: r.code === 0 })), + 'verify script', + loud, + ) +} diff --git a/src/orchestrator/runAll.ts b/src/orchestrator/runAll.ts new file mode 100644 index 0000000..3b08c02 --- /dev/null +++ b/src/orchestrator/runAll.ts @@ -0,0 +1,104 @@ +import { CHECKS } from '../checks/registry.ts' +import { color } from '../shared/color.ts' +import { resolveMode } from '../shared/mode.ts' +import { installConsoleCapture, runCaptured } from '../shared/output.ts' +import { runCommand } from '../shared/spawn.ts' +import { type MeasureRecord, printMeasureTable } from './measure.ts' +import { reportOutcomes } from './report.ts' +import { entryCheckName, resolveEntries, resolveOverride, selectEntries } from './resolveEntries.ts' +import { resolveTestEntry, TEST_CHECK_NAME } from './tests.ts' + +export type RunAllOptions = { measure?: boolean; verbose?: boolean; tests?: boolean } + +type Task = { name: string; note?: string; run: () => Promise } + +/** Build the ordered task list: every built-in (running its override script if defined), then customs, then tests. */ +function buildTasks(opts: RunAllOptions): Task[] { + const entries = resolveEntries() + const mode = resolveMode() + const spawn = (name: string, command: string, cwd: string, note?: string): Task => ({ + name, + note, + run: async () => (await runCommand(command, { cwd, quiet: true })) === 0, + }) + + const tasks: Task[] = CHECKS.map((check) => { + const override = resolveOverride(entries, check.name, mode) + if (!override) return { name: check.name, run: async () => (await check.runDefault()).ok } + return { + name: check.name, + note: 'overridden', + run: async () => { + const ok = (await runCommand(override.command, { cwd: override.cwd, quiet: true })) === 0 + if (!ok) console.error(color.dim(`↳ ${check.name}: ran \`${override.command}\` (override)`)) + return ok + }, + } + }) + + const builtinNames = new Set(CHECKS.map((c) => c.name)) + for (const entry of selectEntries(entries, mode)) { + const check = entryCheckName(entry.name) + if (builtinNames.has(check) || check === TEST_CHECK_NAME) continue + tasks.push(spawn(entry.name, entry.command, entry.cwd, 'custom')) + } + + const testEntry = resolveTestEntry({ noTests: opts.tests === false }) + if (testEntry) tasks.push(spawn(testEntry.name, testEntry.command, testEntry.cwd, 'tests')) + return tasks +} + +/** + * Run every built-in check (the explicit `verifyx all` opt-in) plus any custom `verify:*` scripts and the + * tests step, all in parallel. Each check's output is captured independently so a clean run is silent and + * failures print without interleaving. `--verbose` prints every check's output; `--measure` prints its table. + */ +export async function runAll(opts: RunAllOptions = {}): Promise { + const loud = !!opts.verbose + const tasks = buildTasks(opts) + + if (loud) { + console.log(`Running ${tasks.length} verification(s) in parallel:`) + for (const task of tasks) console.log(` - ${task.name}${task.note ? color.dim(` (${task.note})`) : ''}`) + console.log() + } + + const startTime = Date.now() + const restore = installConsoleCapture() + let runs: Array<{ name: string; ok: boolean; output: string; durationMs: number }> + try { + runs = await Promise.all( + tasks.map(async (task) => { + const taskStart = Date.now() + const { result, output } = await runCaptured(async () => { + try { + return await task.run() + } catch (error) { + console.error(String(error)) + return false + } + }) + return { name: task.name, ok: result, output, durationMs: Date.now() - taskStart } + }), + ) + } finally { + restore() + } + + // Print each check's captured output: everything under --verbose, only failures otherwise. + for (const run of runs) { + if (!loud && run.ok) continue + if (loud) console.log(color.heading(`▶ ${run.name}`)) + if (run.output) process.stdout.write(run.output) + } + + if (opts.measure) { + const records: MeasureRecord[] = runs.map((r) => ({ script: r.name, code: r.ok ? 0 : 1, durationMs: r.durationMs })) + printMeasureTable(records, Date.now() - startTime) + } + return reportOutcomes( + runs.map((r) => ({ name: r.name, ok: r.ok })), + 'verification', + loud, + ) +} diff --git a/src/orchestrator/tests.test.ts b/src/orchestrator/tests.test.ts new file mode 100644 index 0000000..6726051 --- /dev/null +++ b/src/orchestrator/tests.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' + +import { resolveTestScript } from './tests.ts' + +describe('resolveTestScript', () => { + it('prefers an explicit verify:test locally, then the standard test script', () => { + expect(resolveTestScript({ 'verify:test': 'vitest run --silent', test: 'vitest' }, false)).toBe('verify:test') + expect(resolveTestScript({ test: 'vitest' }, false)).toBe('test') + }) + + it('runs only test:ci on CI, never a plain test/verify:test', () => { + expect(resolveTestScript({ 'verify:test': 'x', test: 'y', 'test:ci': 'vitest run --reporter=junit' }, true)).toBe('test:ci') + expect(resolveTestScript({ 'verify:test': 'x', test: 'y' }, true)).toBeNull() + }) + + it('returns null when no applicable script exists', () => { + expect(resolveTestScript({ build: 'tsc' }, false)).toBeNull() + expect(resolveTestScript({}, true)).toBeNull() + }) +}) diff --git a/src/orchestrator/tests.ts b/src/orchestrator/tests.ts new file mode 100644 index 0000000..77650b4 --- /dev/null +++ b/src/orchestrator/tests.ts @@ -0,0 +1,29 @@ +import { loadPackageScripts, type VerifyEntry } from './resolveEntries.ts' + +/** The `verify:*` check name the tests step owns, so it isn't also run in the normal batch. */ +export const TEST_CHECK_NAME = 'test' + +export type TestsOptions = { noTests?: boolean } + +/** + * Which npm script the automatic tests step runs. Locally it prefers an explicit `verify:test`, falling back + * to the standard `test` script. On CI it runs only `test:ci` (CI usually needs a different invocation, e.g. + * emitting junit.xml), so a plain `test` never runs there. Returns null when no applicable script exists. + */ +export function resolveTestScript(scripts: Record, ci: boolean): string | null { + const order = ci ? ['test:ci'] : ['verify:test', 'test'] + return order.find((name) => name in scripts) ?? null +} + +/** + * Resolve the automatic tests step to a runnable entry, so it flows through the same buffered/timed path as + * every other check. Returns null when `--no-tests` is set or no applicable test script exists. + */ +export function resolveTestEntry(opts: TestsOptions = {}): VerifyEntry | null { + if (opts.noTests) return null + const loaded = loadPackageScripts() + if (!loaded) return null + const script = resolveTestScript(loaded.scripts, !!process.env.CI) + if (!script) return null + return { name: script, command: `npm run ${script}`, cwd: loaded.dir } +} diff --git a/src/report.ts b/src/report.ts index b31cbdb..bb48499 100644 --- a/src/report.ts +++ b/src/report.ts @@ -4,17 +4,7 @@ import type { FileScore } from './analyze.ts' import type { CommentBlockViolation } from './comments.ts' import { forEachFunction } from './functions.ts' import { calculateCyclomaticComplexity, calculateHalstead, calculateMaintainabilityIndex, countSloc } from './metrics.ts' - -export const color = { - red: (s: string | number) => `\x1b[31m${s}\x1b[39m`, - green: (s: string | number) => `\x1b[32m${s}\x1b[39m`, - yellow: (s: string | number) => `\x1b[33m${s}\x1b[39m`, - magenta: (s: string | number) => `\x1b[35m${s}\x1b[39m`, - cyan: (s: string | number) => `\x1b[36m${s}\x1b[39m`, - dim: (s: string | number) => `\x1b[2m${s}\x1b[22m`, - bold: (s: string | number) => `\x1b[1m${s}\x1b[22m`, - heading: (s: string | number) => `\x1b[1m\x1b[4m${s}\x1b[24m\x1b[22m`, -} +import { color } from './shared/color.ts' const FORMULA = '171 - 5.2*ln(HalsteadVolume) - 0.23*CyclomaticComplexity - 16.2*ln(SLOC), clamped 0-100' diff --git a/src/scaffold/agentFiles.ts b/src/scaffold/agentFiles.ts new file mode 100644 index 0000000..15ab9e3 --- /dev/null +++ b/src/scaffold/agentFiles.ts @@ -0,0 +1,45 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { ensurePointer, type ManagedFileResult, writeManaged } from './writeManaged.ts' + +export type AgentTarget = 'claude' | 'agents' + +const moduleDir = path.dirname(fileURLToPath(import.meta.url)) +// src/scaffold/*.ts and dist/scaffold/*.mjs both sit two levels below the package root, where templates/ lives. +const TEMPLATES_DIR = path.join(moduleDir, '..', '..', 'templates') + +// A file already telling agents to run `npm run verify` is treated as covered — the pointer is not re-added. +const POINTER_MARKER = 'npm run verify' + +function readTemplate(relativePath: string): string { + return fs.readFileSync(path.join(TEMPLATES_DIR, ...relativePath.split('/')), 'utf-8') +} + +/** + * Emit the `verify` integration for the chosen targets under `cwd`: + * - the same `SKILL.md` goes to Claude (`.claude/skills/verify/`) and the cross-vendor tree + * (`.agent-skills/verify/`), so the integration is identical across agents; + * - a one-line pointer is appended to the matching instruction file (`CLAUDE.md` / `AGENTS.md`). + * + * Skills are CLI-owned (created/updated as a whole). The instruction files are user-owned, so the pointer is + * only appended when absent and existing content is never rewritten. + */ +export function writeAgentFiles(cwd: string, targets: readonly AgentTarget[]): ManagedFileResult[] { + const results: ManagedFileResult[] = [] + const skill = readTemplate('skills/verify/SKILL.md') + const guidance = readTemplate('verify-guidance.md') + + if (targets.includes('claude')) { + writeManaged(path.join(cwd, '.claude', 'skills', 'verify', 'SKILL.md'), skill, results) + ensurePointer(path.join(cwd, 'CLAUDE.md'), guidance, POINTER_MARKER, results) + } + if (targets.includes('agents')) { + writeManaged(path.join(cwd, '.agent-skills', 'verify', 'SKILL.md'), skill, results) + ensurePointer(path.join(cwd, 'AGENTS.md'), guidance, POINTER_MARKER, results) + } + + results.sort((a, b) => a.path.localeCompare(b.path)) + return results +} diff --git a/src/scaffold/init.test.ts b/src/scaffold/init.test.ts new file mode 100644 index 0000000..84cea28 --- /dev/null +++ b/src/scaffold/init.test.ts @@ -0,0 +1,76 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { CHECKS } from '../checks/registry.ts' +import { applyInit } from './init.ts' + +let dir: string + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-init-')) + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'scratch' })) +}) +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }) +}) + +function readScripts(): Record { + return (JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')) as { scripts: Record }).scripts +} + +describe('applyInit', () => { + it('writes verify:* scripts, the verify skill, a CLAUDE.md pointer, and collects external devDeps', () => { + const result = applyInit({ cwd: dir, checks: ['complexity', 'unused-code'], targets: ['claude'], defaultsOnly: false }) + + const scripts = readScripts() + expect(scripts['verify:complexity']).toBe('verifyx complexity') + expect(scripts['verify:unused-code']).toBe('verifyx unused-code') + expect(scripts.verify).toBe('verifyx') + expect(result.devDeps).toContain('knip') + // The skill (not a slash command) is the Claude integration. + expect(fs.existsSync(path.join(dir, '.claude', 'skills', 'verify', 'SKILL.md'))).toBe(true) + expect(fs.existsSync(path.join(dir, '.claude', 'commands'))).toBe(false) + // CLAUDE.md is created with the verify pointer. + expect(fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf-8')).toContain('npm run verify') + }) + + it('defaults-only writes no verify:* scripts, points verify at `verifyx all`, and writes the agents skill + AGENTS.md', () => { + const result = applyInit({ cwd: dir, checks: ['unused-code'], targets: ['agents'], defaultsOnly: true }) + + expect(Object.keys(readScripts())).toEqual(['verify']) + expect(readScripts().verify).toBe('verifyx all') + expect(result.devDeps).toContain('knip') + expect(fs.existsSync(path.join(dir, '.agent-skills', 'verify', 'SKILL.md'))).toBe(true) + expect(fs.readFileSync(path.join(dir, 'AGENTS.md'), 'utf-8')).toContain('npm run verify') + }) + + it('defaults-only with every check installs all external devDeps but writes only the verify script', () => { + const result = applyInit({ cwd: dir, checks: CHECKS.map((c) => c.name), targets: [], defaultsOnly: true }) + + expect(Object.keys(readScripts())).toEqual(['verify']) + expect(readScripts().verify).toBe('verifyx all') + // Every external check's tool is installed, so `verifyx all` can actually run them. + expect(result.devDeps).toEqual(expect.arrayContaining(['knip', 'jscpd', 'skott'])) + }) + + it('does not clobber an existing verify:* script', () => { + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ scripts: { 'verify:complexity': 'custom' } })) + applyInit({ cwd: dir, checks: ['complexity'], targets: [], defaultsOnly: false }) + expect(readScripts()['verify:complexity']).toBe('custom') + }) + + it('teaches knip to ignore the external tools verifyx runs when unused-code is selected', () => { + applyInit({ cwd: dir, checks: ['unused-code', 'lint', 'format'], targets: [], defaultsOnly: false }) + const knip = JSON.parse(fs.readFileSync(path.join(dir, 'knip.json'), 'utf-8')) as { ignoreDependencies?: string[] } + expect(knip.ignoreDependencies).toEqual(expect.arrayContaining(['oxlint', 'oxfmt'])) + expect(knip.ignoreDependencies).not.toContain('knip') // the runner isn't flagged + }) + + it('does not create a knip config when unused-code is not selected', () => { + applyInit({ cwd: dir, checks: ['lint', 'format'], targets: [], defaultsOnly: false }) + expect(fs.existsSync(path.join(dir, 'knip.json'))).toBe(false) + }) +}) diff --git a/src/scaffold/init.ts b/src/scaffold/init.ts new file mode 100644 index 0000000..8b2c496 --- /dev/null +++ b/src/scaffold/init.ts @@ -0,0 +1,54 @@ +import path from 'node:path' + +import { getCheck } from '../checks/registry.ts' +import type { Check } from '../checks/types.ts' +import { type AgentTarget, writeAgentFiles } from './agentFiles.ts' +import { ensureKnipIgnores } from './knipConfig.ts' +import { addVerifyScripts } from './packageScripts.ts' +import type { ManagedFileResult } from './writeManaged.ts' + +export type InitOptions = { + cwd: string + /** Selected check names. */ + checks: readonly string[] + targets: readonly AgentTarget[] + /** When true, do not write `verify:*` scripts — rely on `verify`'s built-in defaults. */ + defaultsOnly: boolean +} + +export type InitResult = { + addedScripts: string[] + /** devDependencies the selected external checks need (deduped). */ + devDeps: string[] + agentFiles: ManagedFileResult[] +} + +/** Pure scaffolding step: write package.json scripts + agent files, and report the devDeps to install. */ +export function applyInit(opts: InitOptions): InitResult { + const devDeps: string[] = [] + const scripts: Record = {} + + for (const name of opts.checks) { + const check = getCheck(name) + if (!check) continue + if (check.scaffold.devDeps) devDeps.push(...check.scaffold.devDeps) + if (!opts.defaultsOnly) scripts[`verify:${name}`] = check.scaffold.script + } + + // Defaults-only wires the top `verify` script to `verifyx all` so it runs every built-in with no verify:* list. + const addedScripts = addVerifyScripts(path.join(opts.cwd, 'package.json'), scripts, opts.defaultsOnly ? 'verifyx all' : 'verifyx') + + const agentFiles = writeAgentFiles(opts.cwd, opts.targets) + + // context: with unused-code selected, teach knip to ignore the other external tools verifyx runs at runtime. + if (opts.checks.includes('unused-code')) { + const toolDeps = opts.checks + .map(getCheck) + .filter((check): check is Check => !!check && check.kind === 'external' && check.name !== 'unused-code') + .flatMap((check) => check.scaffold.devDeps ?? []) + .filter((dep) => dep !== 'typescript') + ensureKnipIgnores(opts.cwd, [...new Set(toolDeps)], agentFiles) + } + + return { addedScripts, devDeps: [...new Set(devDeps)], agentFiles } +} diff --git a/src/scaffold/knipConfig.test.ts b/src/scaffold/knipConfig.test.ts new file mode 100644 index 0000000..1089249 --- /dev/null +++ b/src/scaffold/knipConfig.test.ts @@ -0,0 +1,71 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { ensureKnipIgnores } from './knipConfig.ts' +import type { ManagedFileResult } from './writeManaged.ts' + +let dir: string + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-knip-')) + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'scratch' })) +}) +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }) +}) + +const readKnip = () => JSON.parse(fs.readFileSync(path.join(dir, 'knip.json'), 'utf-8')) as { ignoreDependencies?: string[] } + +describe('ensureKnipIgnores', () => { + it('does nothing when there are no deps to ignore', () => { + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, [], results) + expect(results).toEqual([]) + expect(fs.existsSync(path.join(dir, 'knip.json'))).toBe(false) + }) + + it('creates a minimal knip.json when there is no config', () => { + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, ['oxlint', 'oxfmt'], results) + expect(results[0]?.action).toBe('created') + expect(readKnip().ignoreDependencies).toEqual(['oxlint', 'oxfmt']) + }) + + it('merges missing deps into an existing knip.json without touching other content', () => { + fs.writeFileSync(path.join(dir, 'knip.json'), JSON.stringify({ entry: ['src/x.ts'], ignoreDependencies: ['oxlint'] }, null, 2)) + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, ['oxlint', 'jscpd'], results) + expect(results[0]?.action).toBe('updated') + const cfg = readKnip() as { entry?: string[]; ignoreDependencies?: string[] } + expect(cfg.ignoreDependencies).toEqual(['oxlint', 'jscpd']) // oxlint not duplicated + expect(cfg.entry).toEqual(['src/x.ts']) // untouched + }) + + it('is unchanged when every dep is already ignored', () => { + fs.writeFileSync(path.join(dir, 'knip.json'), JSON.stringify({ ignoreDependencies: ['oxlint', 'oxfmt'] })) + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, ['oxlint'], results) + expect(results[0]?.action).toBe('unchanged') + }) + + it('merges into package.json#knip when present and there is no knip.json', () => { + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'scratch', knip: { ignoreDependencies: ['skott'] } }, null, 2)) + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, ['oxlint'], results) + expect(results[0]?.path).toContain('package.json') + const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')) as { knip: { ignoreDependencies: string[] } } + expect(pkg.knip.ignoreDependencies).toEqual(['skott', 'oxlint']) + expect(fs.existsSync(path.join(dir, 'knip.json'))).toBe(false) + }) + + it('leaves a code-based knip config untouched', () => { + fs.writeFileSync(path.join(dir, 'knip.ts'), 'export default {}\n') + const results: ManagedFileResult[] = [] + ensureKnipIgnores(dir, ['oxlint'], results) + expect(results).toEqual([]) + expect(fs.existsSync(path.join(dir, 'knip.json'))).toBe(false) + }) +}) diff --git a/src/scaffold/knipConfig.ts b/src/scaffold/knipConfig.ts new file mode 100644 index 0000000..13a337c --- /dev/null +++ b/src/scaffold/knipConfig.ts @@ -0,0 +1,61 @@ +import fs from 'node:fs' +import path from 'node:path' + +import type { ManagedFileResult } from './writeManaged.ts' + +// Code-based knip configs are the user's to manage — we won't rewrite JS/TS. +const CODE_CONFIGS = ['knip.ts', 'knip.js', 'knip.config.ts', 'knip.config.js', 'knip.jsonc'] +const SCHEMA = 'https://unpkg.com/knip/schema.json' + +type KnipConfig = { ignoreDependencies?: string[] } & Record + +/** Append any missing `deps` to `existing`, preserving order; report whether anything changed. */ +function addMissing(existing: string[] | undefined, deps: readonly string[]): { list: string[]; changed: boolean } { + const list = [...(existing ?? [])] + let changed = false + for (const dep of deps) { + if (!list.includes(dep)) { + list.push(dep) + changed = true + } + } + return { list, changed } +} + +function mergeInto(config: KnipConfig, deps: readonly string[]): boolean { + const { list, changed } = addMissing(config.ignoreDependencies, deps) + if (changed) config.ignoreDependencies = list + return changed +} + +/** + * Ensure the project's knip config ignores `deps` — the tools verifyx invokes at runtime (via node_modules/.bin), + * which knip can't see and would otherwise flag as unused. Adds only what's missing (idempotent), never removes + * or rewrites unrelated content. Merges into an existing `knip.json` or `package.json#knip`, creates a minimal + * `knip.json` if there's no config, and leaves code-based configs (knip.ts/js) untouched. + */ +export function ensureKnipIgnores(cwd: string, deps: readonly string[], results: ManagedFileResult[]): void { + if (deps.length === 0) return + if (CODE_CONFIGS.some((file) => fs.existsSync(path.join(cwd, file)))) return + + const jsonPath = path.join(cwd, 'knip.json') + if (fs.existsSync(jsonPath)) { + const config = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as KnipConfig + const changed = mergeInto(config, deps) + if (changed) fs.writeFileSync(jsonPath, `${JSON.stringify(config, null, 2)}\n`) + results.push({ path: jsonPath, action: changed ? 'updated' : 'unchanged' }) + return + } + + const pkgPath = path.join(cwd, 'package.json') + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { knip?: KnipConfig } & Record + if (pkg.knip && typeof pkg.knip === 'object') { + const changed = mergeInto(pkg.knip, deps) + if (changed) fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`) + results.push({ path: pkgPath, action: changed ? 'updated' : 'unchanged' }) + return + } + + fs.writeFileSync(jsonPath, `${JSON.stringify({ $schema: SCHEMA, ignoreDependencies: [...deps] }, null, 2)}\n`) + results.push({ path: jsonPath, action: 'created' }) +} diff --git a/src/scaffold/packageScripts.ts b/src/scaffold/packageScripts.ts new file mode 100644 index 0000000..09a4fdf --- /dev/null +++ b/src/scaffold/packageScripts.ts @@ -0,0 +1,28 @@ +import fs from 'node:fs' + +type PackageJson = { scripts?: Record } & Record + +/** + * Add the given `verify:*` scripts to package.json without clobbering existing ones, and ensure a top-level + * `verify` script (`topScript`) that invokes the CLI. Returns the names of scripts actually added. + */ +export function addVerifyScripts(packageJsonPath: string, scripts: Record, topScript = 'verifyx'): string[] { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageJson + const existing = pkg.scripts ?? {} + const added: string[] = [] + + for (const [name, body] of Object.entries(scripts)) { + if (existing[name] === undefined) { + existing[name] = body + added.push(name) + } + } + if (existing.verify === undefined) { + existing.verify = topScript + added.push('verify') + } + + pkg.scripts = existing + fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`) + return added +} diff --git a/src/scaffold/writeManaged.test.ts b/src/scaffold/writeManaged.test.ts new file mode 100644 index 0000000..028f4c4 --- /dev/null +++ b/src/scaffold/writeManaged.test.ts @@ -0,0 +1,104 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { ensurePointer, type ManagedFileResult, summarise, writeManaged } from './writeManaged.ts' + +let dir: string + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-managed-')) +}) +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }) +}) + +describe('writeManaged', () => { + it('creates a file (and parent dirs), then reports unchanged, then updated', () => { + const file = path.join(dir, 'nested', 'file.md') + + const created: ManagedFileResult[] = [] + writeManaged(file, 'a', created) + expect(created[0]?.action).toBe('created') + expect(fs.readFileSync(file, 'utf-8')).toBe('a') + + const unchanged: ManagedFileResult[] = [] + writeManaged(file, 'a', unchanged) + expect(unchanged[0]?.action).toBe('unchanged') + + const updated: ManagedFileResult[] = [] + writeManaged(file, 'b', updated) + expect(updated[0]?.action).toBe('updated') + expect(fs.readFileSync(file, 'utf-8')).toBe('b') + }) + + it('refuses to write through a symlink', () => { + const target = path.join(dir, 'target.md') + fs.writeFileSync(target, 'x') + const link = path.join(dir, 'link.md') + try { + fs.symlinkSync(target, link) + } catch { + return // symlink creation is privileged on some platforms — skip + } + expect(() => writeManaged(link, 'y', [])).toThrow(/symlink/) + }) + + it('summarise counts each action', () => { + expect( + summarise([ + { path: 'a', action: 'created' }, + { path: 'b', action: 'unchanged' }, + { path: 'c', action: 'created' }, + { path: 'd', action: 'appended' }, + ]), + ).toEqual({ created: 2, updated: 0, unchanged: 1, appended: 1 }) + }) +}) + +describe('ensurePointer', () => { + const block = '## Verification\n\nRun `/verify` and fix what it reports.\n' + + it('creates the file with the block when missing', () => { + const file = path.join(dir, 'CLAUDE.md') + const results: ManagedFileResult[] = [] + ensurePointer(file, block, '/verify', results) + expect(results[0]?.action).toBe('created') + expect(fs.readFileSync(file, 'utf-8')).toBe(block) + }) + + it('leaves the file untouched when the marker is already present', () => { + const file = path.join(dir, 'CLAUDE.md') + const original = '# My project\n\nSome guidance mentioning /verify already.\n' + fs.writeFileSync(file, original) + const results: ManagedFileResult[] = [] + ensurePointer(file, block, '/verify', results) + expect(results[0]?.action).toBe('unchanged') + expect(fs.readFileSync(file, 'utf-8')).toBe(original) + }) + + it('appends the block (keeping existing content) when the marker is absent', () => { + const file = path.join(dir, 'CLAUDE.md') + const original = '# My project\n\nExisting instructions.\n' + fs.writeFileSync(file, original) + const results: ManagedFileResult[] = [] + ensurePointer(file, block, '/verify', results) + expect(results[0]?.action).toBe('appended') + const after = fs.readFileSync(file, 'utf-8') + expect(after.startsWith(original)).toBe(true) + expect(after).toContain(block) + }) + + it('is idempotent — a second call after appending is a no-op', () => { + const file = path.join(dir, 'CLAUDE.md') + fs.writeFileSync(file, '# My project\n') + ensurePointer(file, block, '/verify', []) + const afterFirst = fs.readFileSync(file, 'utf-8') + const results: ManagedFileResult[] = [] + ensurePointer(file, block, '/verify', results) + expect(results[0]?.action).toBe('unchanged') + expect(fs.readFileSync(file, 'utf-8')).toBe(afterFirst) + }) +}) diff --git a/src/scaffold/writeManaged.ts b/src/scaffold/writeManaged.ts new file mode 100644 index 0000000..222fcaf --- /dev/null +++ b/src/scaffold/writeManaged.ts @@ -0,0 +1,79 @@ +import fs from 'node:fs' +import path from 'node:path' + +// Ported from https://github.com/MakerXStudio data-streams CLI upgrade-docs.ts writeManaged. +type ManagedAction = 'unchanged' | 'updated' | 'created' | 'appended' + +export type ManagedFileResult = { + path: string + action: ManagedAction +} + +/** One-character prefix shown per action in scaffold reports. */ +export const ACTION_MARK: Record = { created: '+', appended: '»', updated: '~', unchanged: ' ' } + +function readIfExists(file: string): string | null { + if (!fs.existsSync(file)) return null + return fs.readFileSync(file, 'utf-8') +} + +function createFile(file: string, contents: string, results: ManagedFileResult[]): void { + fs.mkdirSync(path.dirname(file), { recursive: true }) + fs.writeFileSync(file, contents, { encoding: 'utf8', flag: 'wx' }) + results.push({ path: file, action: 'created' }) +} + +/** Guard before overwriting an existing path: never write through a symlink or over a non-regular file. */ +function assertRegularFile(file: string): void { + const stat = fs.lstatSync(file) + if (stat.isSymbolicLink()) throw new Error(`Refusing to write through symlink at ${file}`) + if (!stat.isFile()) throw new Error(`Refusing to write to non-regular file at ${file}`) +} + +/** + * Idempotently write a CLI-managed file: create if missing, rewrite if changed, leave alone if identical. + * Refuses to write through a symlink or over a non-regular file. + */ +export function writeManaged(file: string, contents: string, results: ManagedFileResult[]): void { + const existing = readIfExists(file) + if (existing === null) return createFile(file, contents, results) + if (existing === contents) { + results.push({ path: file, action: 'unchanged' }) + return + } + assertRegularFile(file) + fs.writeFileSync(file, contents, { encoding: 'utf8', flag: 'w' }) + results.push({ path: file, action: 'updated' }) +} + +/** + * Ensure a user-owned file (CLAUDE.md / AGENTS.md) contains a pointer block. Creates the file with the block + * when missing; appends the block when the file exists but does not already contain `marker`; otherwise leaves + * the file untouched. Never rewrites existing content. + */ +export function ensurePointer(file: string, block: string, marker: string, results: ManagedFileResult[]): void { + const existing = readIfExists(file) + if (existing === null) return createFile(file, block, results) + if (existing.includes(marker)) { + results.push({ path: file, action: 'unchanged' }) + return + } + assertRegularFile(file) + const separator = existing.endsWith('\n') ? '\n' : '\n\n' + fs.writeFileSync(file, existing + separator + block, { encoding: 'utf8', flag: 'w' }) + results.push({ path: file, action: 'appended' }) +} + +export function summarise(results: readonly ManagedFileResult[]): { + created: number + updated: number + unchanged: number + appended: number +} { + return { + created: results.filter((r) => r.action === 'created').length, + updated: results.filter((r) => r.action === 'updated').length, + unchanged: results.filter((r) => r.action === 'unchanged').length, + appended: results.filter((r) => r.action === 'appended').length, + } +} diff --git a/src/shared/color.ts b/src/shared/color.ts new file mode 100644 index 0000000..6e52610 --- /dev/null +++ b/src/shared/color.ts @@ -0,0 +1,10 @@ +export const color = { + red: (s: string | number) => `\x1b[31m${s}\x1b[39m`, + green: (s: string | number) => `\x1b[32m${s}\x1b[39m`, + yellow: (s: string | number) => `\x1b[33m${s}\x1b[39m`, + magenta: (s: string | number) => `\x1b[35m${s}\x1b[39m`, + cyan: (s: string | number) => `\x1b[36m${s}\x1b[39m`, + dim: (s: string | number) => `\x1b[2m${s}\x1b[22m`, + bold: (s: string | number) => `\x1b[1m${s}\x1b[22m`, + heading: (s: string | number) => `\x1b[1m\x1b[4m${s}\x1b[24m\x1b[22m`, +} diff --git a/src/shared/comment-scan.ts b/src/shared/comment-scan.ts new file mode 100644 index 0000000..2ffe9ed --- /dev/null +++ b/src/shared/comment-scan.ts @@ -0,0 +1,65 @@ +import fs from 'node:fs' +import path from 'node:path' + +import ts from 'typescript' + +export type ScannedComment = { line: number; text: string } + +const YAML_EXTENSIONS = ['.yml', '.yaml'] + +function isYamlFile(file: string): boolean { + return YAML_EXTENSIONS.some((ext) => file.endsWith(ext)) +} + +function scriptKindFor(file: string): ts.ScriptKind { + return file.endsWith('.tsx') || file.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS +} + +function scanCodeComments(file: string, content: string): ScannedComment[] { + const sourceFile = ts.createSourceFile(path.basename(file), content, ts.ScriptTarget.Latest, true, scriptKindFor(file)) + const seen = new Set() + const out: ScannedComment[] = [] + const addRanges = (ranges: readonly ts.CommentRange[] | undefined): void => { + for (const range of ranges ?? []) { + if (seen.has(range.pos)) continue + seen.add(range.pos) + const { line } = sourceFile.getLineAndCharacterOfPosition(range.pos) + out.push({ line: line + 1, text: content.slice(range.pos, range.end) }) + } + } + const visit = (node: ts.Node): void => { + addRanges(ts.getLeadingCommentRanges(content, node.getFullStart())) + addRanges(ts.getTrailingCommentRanges(content, node.getEnd())) + ts.forEachChild(node, visit) + } + visit(sourceFile) + out.sort((a, b) => a.line - b.line) + return out +} + +/** A `#` starts a YAML comment only at line start or after whitespace, and never inside a quoted scalar. */ +function scanYamlComments(content: string): ScannedComment[] { + const out: ScannedComment[] = [] + const lines = content.split('\n') + for (let i = 0; i < lines.length; i++) { + const line = lines[i] as string + let inSingle = false + let inDouble = false + for (let j = 0; j < line.length; j++) { + const ch = line[j] + if (ch === "'" && !inDouble) inSingle = !inSingle + else if (ch === '"' && !inSingle) inDouble = !inDouble + else if (ch === '#' && !inSingle && !inDouble && (j === 0 || /\s/.test(line[j - 1] as string))) { + out.push({ line: i + 1, text: line.slice(j) }) + break + } + } + } + return out +} + +/** Extract every comment (with its 1-based line) from a source file, dispatching on extension. */ +export function scanFileComments(file: string): ScannedComment[] { + const content = fs.readFileSync(file, 'utf-8') + return isYamlFile(file) ? scanYamlComments(content) : scanCodeComments(file, content) +} diff --git a/src/shared/config.ts b/src/shared/config.ts new file mode 100644 index 0000000..b277c81 --- /dev/null +++ b/src/shared/config.ts @@ -0,0 +1,39 @@ +import fs from 'node:fs' +import path from 'node:path' + +/** A rule for the forbidden-strings check: JSON values at `paths` in `file` must not match `disallowed`. */ +export type ForbiddenStringsRule = { + file: string + paths: string[] + disallowed: string +} + +export type VerifyConfig = { + comments?: { ignore?: string[] } + hardcodedColors?: { ignore?: string[]; root?: string } + forbiddenStrings?: ForbiddenStringsRule[] +} + +const CONFIG_FILE = 'verify.config.json' + +function readJsonIfExists(file: string): unknown { + if (!fs.existsSync(file)) return undefined + try { + return JSON.parse(fs.readFileSync(file, 'utf-8')) + } catch { + return undefined + } +} + +/** Load per-repo check configuration from `verify.config.json`, falling back to a `verify` key in package.json. */ +export function loadVerifyConfig(cwd: string = process.cwd()): VerifyConfig { + const fromFile = readJsonIfExists(path.join(cwd, CONFIG_FILE)) + if (fromFile && typeof fromFile === 'object') return fromFile as VerifyConfig + + const pkg = readJsonIfExists(path.join(cwd, 'package.json')) + if (pkg && typeof pkg === 'object' && 'verify' in pkg) { + const verify = (pkg as { verify?: unknown }).verify + if (verify && typeof verify === 'object') return verify as VerifyConfig + } + return {} +} diff --git a/src/shared/diff.test.ts b/src/shared/diff.test.ts new file mode 100644 index 0000000..d6788df --- /dev/null +++ b/src/shared/diff.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' + +import { parseDiffAddedLines } from './diff.ts' + +const DIFF = `diff --git a/src/a.ts b/src/a.ts +index 111..222 100644 +--- a/src/a.ts ++++ b/src/a.ts +@@ -1,2 +1,3 @@ + const x = 1 ++const y = 2 + const z = 3 +diff --git a/src/new.ts b/src/new.ts +new file mode 100644 +--- /dev/null ++++ b/src/new.ts +@@ -0,0 +1,2 @@ ++export const a = 1 ++export const b = 2 +` + +describe('parseDiffAddedLines', () => { + it('maps changed files to their added line numbers', () => { + const added = parseDiffAddedLines(DIFF) + expect([...(added.get('src/a.ts') ?? [])]).toEqual([2]) + expect([...(added.get('src/new.ts') ?? [])]).toEqual([1, 2]) + }) + + it('ignores deletions (/dev/null targets)', () => { + const del = `--- a/gone.ts\n+++ /dev/null\n@@ -1,1 +0,0 @@\n-const gone = 1\n` + expect(parseDiffAddedLines(del).size).toBe(0) + }) + + it('returns an empty map for an empty diff', () => { + expect(parseDiffAddedLines('').size).toBe(0) + }) +}) diff --git a/src/shared/diff.ts b/src/shared/diff.ts new file mode 100644 index 0000000..a565394 --- /dev/null +++ b/src/shared/diff.ts @@ -0,0 +1,43 @@ +// Ported from https://github.com/staff0rd/assist verify/blockComments/parseDiffAddedLines.ts +export type AddedLines = Map> + +const FILE_HEADER = /^\+\+\+ (?:b\/)?(.+)$/ +const HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/ + +/** Map each file in a unified diff to the set of 1-based line numbers that were added or changed. */ +export function parseDiffAddedLines(diff: string): AddedLines { + const added: AddedLines = new Map() + let currentFile: string | null = null + let newLine = 0 + + for (const line of diff.split('\n')) { + const fileMatch = line.match(FILE_HEADER) + if (fileMatch) { + const file = fileMatch[1] as string + currentFile = file === '/dev/null' ? null : file + continue + } + + const hunkMatch = line.match(HUNK_HEADER) + if (hunkMatch) { + newLine = Number(hunkMatch[1]) + continue + } + + if (currentFile === null) continue + + if (line.startsWith('+')) { + let set = added.get(currentFile) + if (!set) { + set = new Set() + added.set(currentFile, set) + } + set.add(newLine) + newLine++ + } else if (!line.startsWith('-')) { + newLine++ + } + } + + return added +} diff --git a/src/shared/git.ts b/src/shared/git.ts new file mode 100644 index 0000000..bc8914b --- /dev/null +++ b/src/shared/git.ts @@ -0,0 +1,23 @@ +import { execSync } from 'node:child_process' + +function run(command: string): string { + try { + return execSync(command, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim() + } catch { + return '' + } +} + +// context: locally we diff the working tree against HEAD (uncommitted changes). A CI checkout has nothing +// uncommitted, so there we diff against the merge base with the PR base branch, else the gate is a no-op. +function resolveDiffBase(): string { + if (!process.env.CI) return 'HEAD' + const base = process.env.VERIFY_DIFF_BASE || (process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : '') + if (!base) return 'HEAD' + return run(`git merge-base ${base} HEAD`) || 'HEAD' +} + +/** Diff used to detect new comments: working tree vs HEAD locally, vs the PR merge base in CI. '' on error. */ +export function gitDiffAgainstBase(): string { + return run(`git diff ${resolveDiffBase()}`) +} diff --git a/src/shared/mode.test.ts b/src/shared/mode.test.ts new file mode 100644 index 0000000..590f28f --- /dev/null +++ b/src/shared/mode.test.ts @@ -0,0 +1,64 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { configureMode, resolveMode } from './mode.ts' + +const saved: { ci?: string; mode?: string } = {} + +beforeEach(() => { + saved.ci = process.env.CI + saved.mode = process.env.VERIFY_MODE + delete process.env.CI + delete process.env.VERIFY_MODE +}) +afterEach(() => { + if (saved.ci === undefined) delete process.env.CI + else process.env.CI = saved.ci + if (saved.mode === undefined) delete process.env.VERIFY_MODE + else process.env.VERIFY_MODE = saved.mode +}) + +describe('resolveMode', () => { + it('defaults to fix locally (no CI, no override)', () => { + expect(resolveMode()).toBe('fix') + }) + + it('is check under CI', () => { + process.env.CI = 'true' + expect(resolveMode()).toBe('check') + }) + + it('lets VERIFY_MODE override CI (fix in CI)', () => { + process.env.CI = 'true' + process.env.VERIFY_MODE = 'fix' + expect(resolveMode()).toBe('fix') + }) + + it('honours VERIFY_MODE=check with no CI', () => { + process.env.VERIFY_MODE = 'check' + expect(resolveMode()).toBe('check') + }) + + it('ignores an unrecognised VERIFY_MODE value', () => { + process.env.VERIFY_MODE = 'nonsense' + expect(resolveMode()).toBe('fix') + }) +}) + +describe('configureMode', () => { + it('sets VERIFY_MODE=check for --check so it propagates to child scripts', () => { + configureMode({ check: true }) + expect(process.env.VERIFY_MODE).toBe('check') + expect(resolveMode()).toBe('check') + }) + + it('sets VERIFY_MODE=fix for --fix, overriding CI', () => { + process.env.CI = 'true' + configureMode({ fix: true }) + expect(resolveMode()).toBe('fix') + }) + + it('leaves the environment untouched when neither flag is given', () => { + configureMode({}) + expect(process.env.VERIFY_MODE).toBeUndefined() + }) +}) diff --git a/src/shared/mode.ts b/src/shared/mode.ts new file mode 100644 index 0000000..92d79ea --- /dev/null +++ b/src/shared/mode.ts @@ -0,0 +1,19 @@ +import type { CheckMode } from '../checks/types.ts' + +/** + * Resolve the run mode. The AI/human running `verify` locally gets `fix` (auto-fix, no token churn); + * CI gets `check` (fail on issues, never rewrite). An explicit `--check`/`--fix` sets `VERIFY_MODE`, + * which wins; otherwise a truthy `CI` env means check. + */ +export function resolveMode(): CheckMode { + const explicit = process.env.VERIFY_MODE + if (explicit === 'check' || explicit === 'fix') return explicit + if (process.env.CI) return 'check' + return 'fix' +} + +/** Apply a `--check`/`--fix` override so it propagates to in-process checks and spawned child scripts. */ +export function configureMode(opts?: { check?: boolean; fix?: boolean }): void { + if (opts?.check) process.env.VERIFY_MODE = 'check' + else if (opts?.fix) process.env.VERIFY_MODE = 'fix' +} diff --git a/src/shared/output.ts b/src/shared/output.ts new file mode 100644 index 0000000..172eceb --- /dev/null +++ b/src/shared/output.ts @@ -0,0 +1,38 @@ +import { AsyncLocalStorage } from 'node:async_hooks' +import util from 'node:util' + +// A per-task buffer set by runCaptured; concurrent tasks each get their own, so parallel output never interleaves. +const captureStore = new AsyncLocalStorage() + +/** True while running inside runCaptured (output is being collected rather than written straight out). */ +export function isCapturing(): boolean { + return captureStore.getStore() !== undefined +} + +/** Write text to the active capture buffer if one is set, else straight to the real stdout/stderr stream. */ +export function emit(text: string, stream: 'out' | 'err' = 'out'): void { + const sink = captureStore.getStore() + if (sink) sink.push(text) + else if (stream === 'err') process.stderr.write(text) + else process.stdout.write(text) +} + +/** Patch console.* to route through emit(), so output produced anywhere in a task is captured. Returns a restore fn. */ +export function installConsoleCapture(): () => void { + const { log, warn, error } = console + console.log = (...args: unknown[]): void => emit(`${util.format(...args)}\n`, 'out') + console.warn = (...args: unknown[]): void => emit(`${util.format(...args)}\n`, 'err') + console.error = (...args: unknown[]): void => emit(`${util.format(...args)}\n`, 'err') + return () => { + console.log = log + console.warn = warn + console.error = error + } +} + +/** Run `fn` with everything it emits (via console.* or emit()) collected into a string instead of printed. */ +export async function runCaptured(fn: () => Promise): Promise<{ result: T; output: string }> { + const sink: string[] = [] + const result = await captureStore.run(sink, fn) + return { result, output: sink.join('') } +} diff --git a/src/shared/spawn.ts b/src/shared/spawn.ts new file mode 100644 index 0000000..09783e8 --- /dev/null +++ b/src/shared/spawn.ts @@ -0,0 +1,53 @@ +import { spawn } from 'node:child_process' + +import { emit, isCapturing } from './output.ts' + +let verboseMode = false + +/** When verbose, per-command output is always streamed; otherwise it is buffered and only shown on failure. */ +export function setVerbose(verbose: boolean): void { + verboseMode = verbose +} + +export function isVerbose(): boolean { + return verboseMode +} + +function shouldSuppress(quiet?: boolean): boolean { + if (verboseMode) return false + return !!quiet || !!process.env.CLAUDECODE +} + +export type RunCommandOptions = { + cwd?: string + env?: Record + quiet?: boolean +} + +/** + * Run a shell command, returning its exit code. Suppressed output (quiet, or under Claude Code) is buffered + * and flushed to stdout only if the command fails, keeping passing runs quiet. + */ +export function runCommand(command: string, opts: RunCommandOptions = {}): Promise { + return new Promise((resolve) => { + const suppress = shouldSuppress(opts.quiet) + const child = spawn(command, [], { + stdio: suppress ? 'pipe' : 'inherit', + shell: true, + cwd: opts.cwd ?? process.cwd(), + env: opts.env ? { ...process.env, ...opts.env } : undefined, + }) + const chunks: Buffer[] = [] + if (suppress) { + child.stdout?.on('data', (data: Buffer) => chunks.push(data)) + child.stderr?.on('data', (data: Buffer) => chunks.push(data)) + } + child.on('close', (code) => { + const exitCode = code ?? 1 + // When capturing (parallel `verifyx all`), hand all output to the buffer; otherwise flush only on failure. + if (suppress && chunks.length > 0 && (isCapturing() || exitCode !== 0)) emit(Buffer.concat(chunks).toString()) + resolve(exitCode) + }) + child.on('error', () => resolve(127)) + }) +} diff --git a/templates/skills/verify/SKILL.md b/templates/skills/verify/SKILL.md new file mode 100644 index 0000000..3d5e0f7 --- /dev/null +++ b/templates/skills/verify/SKILL.md @@ -0,0 +1,6 @@ +--- +name: verify +description: Run this project's code verifications and fix what they report. Use after making code changes, or when asked to "verify", "run checks", or "run verify". +--- + +Run `npm run verify` (or `npx verifyx` — the CLI binary is `verifyx`, since `verify` is a Windows builtin). If it fails, fix every error and run again until it passes. Don't silence checks or game the metrics. diff --git a/templates/verify-guidance.md b/templates/verify-guidance.md new file mode 100644 index 0000000..e73171a --- /dev/null +++ b/templates/verify-guidance.md @@ -0,0 +1,3 @@ +## Verification + +After making code changes, run `npm run verify` and fix everything it reports before finishing. Don't silence checks or game the metrics.