diff --git a/README.md b/README.md index b6ccfaf..db3a115 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ Configuration lives at exactly one of two levels: in the package (`eslint.config ## Packages -Packages are listed here as they are added, each linking to its own README. +| Package | What it is | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [`graph-dag`](packages/core/graph/dag/README.md) | Model-independent dependency-graph primitives over a node set and a `needs` accessor: layers, ancestors, descendants, cycles, transitive reduction, critical path. | ## Conventions diff --git a/knip.config.ts b/knip.config.ts index d96284d..33fac3f 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -9,7 +9,12 @@ const config: KnipConfig = { workspaces: { ".": { entry: ["*.config.ts"], - project: ["*.config.ts", "commit-types.ts", "eslint.shared.ts"], + project: [ + "*.config.ts", + "commit-types.ts", + "eslint.shared.ts", + "stryker.shared.ts", + ], }, "packages/core/*/*": { entry: ["src/index.{ts,tsx}"], diff --git a/package.json b/package.json index 5c63b99..096225c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@semantic-release/github": "12.0.10", "@semantic-release/npm": "13.2.0", "@semantic-release/release-notes-generator": "14.1.1", + "@stryker-mutator/api": "10.0.0", "@types/node": "26.6.4", "conventional-changelog-conventionalcommits": "9.3.1", "eslint": "10.12.0", @@ -70,6 +71,6 @@ "turbo": "2.11.7", "typescript": "6.0.3", "typescript-eslint": "8.71.0", - "vitest": "5.0.3" + "vitest": "4.1.11" } } diff --git a/packages/core/graph/dag/AGENTS.md b/packages/core/graph/dag/AGENTS.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/packages/core/graph/dag/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/core/graph/dag/CLAUDE.md b/packages/core/graph/dag/CLAUDE.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/packages/core/graph/dag/CLAUDE.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/core/graph/dag/README.md b/packages/core/graph/dag/README.md new file mode 100644 index 0000000..b0f19c3 --- /dev/null +++ b/packages/core/graph/dag/README.md @@ -0,0 +1,78 @@ +# @exadev/process-graph-dag + +Dependency-graph primitives over any node set and a `needs` accessor: topological layers, ancestors, descendants, cycle detection, transitive reduction and the critical path. No runtime dependencies, no knowledge of any process model, and no recursion, so a deep graph cannot overflow the call stack. `src/` imports nothing Node-only and runs unchanged in a browser or a Worker. + +A graph is built once from the data you already have, then analysed with plain functions: + +```ts +import { + buildDag, + criticalPath, + findCycles, + topologicalLayers, +} from "@exadev/process-graph-dag"; + +const jobs: Record = { + build: [], + lint: [], + test: ["build"], + release: ["test", "lint"], +}; + +const dag = buildDag(Object.keys(jobs), (id) => jobs[id] ?? []); + +topologicalLayers(dag).layers; // [["build", "lint"], ["test"], ["release"]] +findCycles(dag); // [] +criticalPath(dag, () => 1); // { path: ["build", "test", "release"], weight: 3 } +``` + +## Model + +Ids are `string | number`, so two ids name the same node exactly when they are strictly equal. `needs(id)` lists the nodes that must come before `id`. Every ordering this package produces derives from the order `nodes` was iterated in, so results are deterministic and stable. + +Nothing is absorbed silently: + +- **Unknown references.** A `needs` target that is not a node is not an edge. It is returned as data in `dag.unknownReferences`, one `{ from, missing }` per distinct pair. +- **Cycles.** `buildDag` never throws for a cycle. `findCycles` reports them with their paths, and each analysis states below what it does when one exists. +- **Duplicates.** Repeated nodes and repeated needs collapse to their first occurrence. A node that needs itself keeps that edge and is a cycle of one. + +## API + +| Function | Returns | With a cycle | +| --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| `buildDag(nodes, needs)` | An immutable `Dag`: nodes, needs, dependents, unknown references | Builds normally. | +| `topologicalLayers(dag)` | `levels`, `layers`, `unlayered` | Layers the acyclic part; nodes on a cycle or needing one are returned in `unlayered`. | +| `ancestorsOf(dag, id)` | Transitive needs, in node order | Includes `id` itself when it is on a cycle. | +| `descendantsOf(dag, id)` | Transitive dependents, in node order | Includes `id` itself when it is on a cycle. | +| `findCycles(dag)` | One closed path per cyclic strongly connected component | Not applicable; this is the report. | +| `transitiveReduction(dag)` | A `Dag` without redundant edges | Throws `CycleError`: with a cycle the reduction is not unique. | +| `criticalPath(dag, weight)` | `{ path, weight }`, the heaviest chain | Throws `CycleError`: a chain through a cycle has no greatest weight. | + +`ancestorsOf` and `descendantsOf` throw `UnknownNodeError` for an id that is not a node. `criticalPath` throws `RangeError` for a negative or non-finite weight. + +### Layers + +A node's level is the length of the longest `needs` chain below it: a node with no needs is level 0, every other node is one more than its highest-level need. `layers[n]` holds the level-n nodes in node order; each needs only earlier layers, so a layer can run in parallel once the previous ones are done. Unknown references contribute nothing to a level. + +### Cycles + +`findCycles` returns one path per strongly connected component that contains a cycle (more than one node, or a self-loop). A path lists nodes so that each needs the next and the last needs the first, repeated at the end: `["a", "b", "c", "a"]` means a needs b, b needs c and c needs a. A self-loop is `["a", "a"]`. + +Which cycle is reported for a component is fixed: a shortest cycle through the component's first node in node order, ties going to the earliest needs. Paths are ordered by the position of their first node. A component may hold more cycles than the one reported; break the reported one and call again to find the rest. + +### Transitive reduction + +An edge from a node to one of its needs is redundant when another of its needs already depends on that need. The reduction removes every redundant edge, keeps each node's remaining needs in their original order, and has the same ancestors and descendants as the input. Unknown references pass through unchanged. + +### Critical path + +The path with the greatest total node weight along `needs`, returned in dependency order. `weight(id)` is called once per node, in node order. The path always runs back to a node with no needs. Ties are broken by node order: among equally heavy needs the earliest node is followed, and among equally heavy end nodes the earliest ends the path. An empty graph returns `{ path: [], weight: 0 }`. + +## Commands + +```sh +pnpm build # tsc -b -> dist/ (ESM + .d.ts) +pnpm test # unit tests +pnpm test:smoke # the built dist/ exercised as a real artefact +pnpm test:mutation # Stryker +``` diff --git a/packages/core/graph/dag/eslint.config.ts b/packages/core/graph/dag/eslint.config.ts new file mode 100644 index 0000000..4c43201 --- /dev/null +++ b/packages/core/graph/dag/eslint.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "eslint/config"; +import { packageLintConfig } from "../../../../eslint.shared.ts"; + +export default defineConfig( + packageLintConfig({ + tsconfigRootDir: import.meta.dirname, + isomorphic: true, + }), +); diff --git a/packages/core/graph/dag/package.json b/packages/core/graph/dag/package.json new file mode 100644 index 0000000..2a11aec --- /dev/null +++ b/packages/core/graph/dag/package.json @@ -0,0 +1,71 @@ +{ + "name": "@exadev/process-graph-dag", + "version": "0.0.0", + "description": "Model-independent dependency-graph primitives over a node set and a needs accessor: layers, ancestors, descendants, cycles, transitive reduction and the critical path.", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/ExaDev/process-graph.git", + "directory": "packages/core/graph/dag" + }, + "homepage": "https://github.com/ExaDev/process-graph/tree/main/packages/core/graph/dag#readme", + "bugs": { + "url": "https://github.com/ExaDev/process-graph/issues" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org/" + }, + "sideEffects": false, + "license": "MIT", + "scripts": { + "build": "turbo run _build", + "_build": "tsc -b tsconfig.build.json", + "lint": "turbo run _lint", + "_lint": "eslint . --cache --max-warnings 0", + "_lint:fix": "eslint . --fix --cache --max-warnings 0", + "typecheck": "turbo run _typecheck _typecheck:attw", + "_typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.node.json --noEmit", + "_typecheck:attw": "attw --pack --profile esm-only", + "test": "turbo run _test", + "_test": "vitest run --project unit", + "test:coverage": "turbo run _test:coverage", + "_test:coverage": "vitest run --project unit --coverage", + "test:smoke": "turbo run _test:smoke", + "_test:smoke": "vitest run --project smoke", + "test:mutation": "turbo run _test:mutation", + "_test:mutation": "stryker run stryker.config.ts", + "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build && publint && attw --pack --profile esm-only" + }, + "keywords": [ + "dag", + "dependency-graph", + "topological-sort", + "critical-path", + "transitive-reduction", + "cycle-detection" + ], + "devDependencies": { + "@arethetypeswrong/cli": "0.18.5", + "@stryker-mutator/core": "10.0.0", + "@stryker-mutator/typescript-checker": "10.0.0", + "@stryker-mutator/vitest-runner": "10.0.0", + "@types/node": "26.6.4", + "@vitest/coverage-v8": "4.1.11", + "eslint": "10.12.0", + "publint": "0.3.25", + "typescript": "6.0.3", + "vitest": "4.1.11" + } +} diff --git a/packages/core/graph/dag/src/critical-path.ts b/packages/core/graph/dag/src/critical-path.ts new file mode 100644 index 0000000..c7c4f52 --- /dev/null +++ b/packages/core/graph/dag/src/critical-path.ts @@ -0,0 +1,88 @@ +import { assertAcyclic } from "./cycles.ts"; +import { type Dag, type NodeId, lookup } from "./dag.ts"; +import { topologicalLayers } from "./layers.ts"; + +/** + * The most expensive chain through a graph. + */ +export interface CriticalPath { + /** The chain in dependency order: each node needs the one before it. Empty only for a graph with no nodes. */ + readonly path: readonly Id[]; + /** The sum of the weights of the nodes on `path`. */ + readonly weight: number; +} + +/** + * The longest weighted chain along `needs`, where a chain's weight is the sum of its nodes' weights. + * + * `weight` is called exactly once per node, in node order, and must return a finite number that is not negative. The returned path always runs back to a node with no needs and ends at the node whose chain is heaviest. + * + * Ties are broken deterministically, and the rule is part of the contract. Where several needs of a node give an equally heavy chain, the one that comes first in node order is followed. Where several nodes end an equally heavy chain, the one that comes first in node order ends it. A zero-weight node therefore never shortens the path back to the start, and a zero-weight dependent never extends the path past an earlier node of equal total. + * + * Defined for acyclic graphs. An empty graph returns `{ path: [], weight: 0 }`. Unknown references are not edges. + * @throws CycleError when the graph contains a cycle: a chain through a cycle has no greatest weight. + * @throws RangeError when `weight` returns a negative or non-finite number. + */ +export function criticalPath( + dag: Dag, + weight: (id: Id) => number, +): CriticalPath { + assertAcyclic(dag); + const weights = new Map(); + for (const id of dag.nodes) { + const value = weight(id); + if (!Number.isFinite(value) || value < 0) { + throw new RangeError( + `The weight of "${String(id)}" must be a finite, non-negative number, got ${String(value)}`, + ); + } + weights.set(id, value); + } + + const position = new Map(dag.nodes.map((id, order) => [id, order])); + const total = new Map(); + const previous = new Map(); + for (const layer of topologicalLayers(dag).layers) { + for (const id of layer) { + let best: Id | undefined; + let bestTotal = 0; + for (const need of lookup(dag.needs, id)) { + const candidate = lookup(total, need); + if ( + best === undefined || + candidate > bestTotal || + (candidate === bestTotal && + lookup(position, need) < lookup(position, best)) + ) { + best = need; + bestTotal = candidate; + } + } + total.set(id, lookup(weights, id) + bestTotal); + if (best !== undefined) { + previous.set(id, best); + } + } + } + + let end: Id | undefined; + for (const id of dag.nodes) { + if (end === undefined || lookup(total, id) > lookup(total, end)) { + end = id; + } + } + if (end === undefined) { + return { path: [], weight: 0 }; + } + + const path: Id[] = []; + for ( + let node: Id | undefined = end; + node !== undefined; + node = previous.get(node) + ) { + path.push(node); + } + + return { path: path.reverse(), weight: lookup(total, end) }; +} diff --git a/packages/core/graph/dag/src/critical-path.unit.test.ts b/packages/core/graph/dag/src/critical-path.unit.test.ts new file mode 100644 index 0000000..45d199f --- /dev/null +++ b/packages/core/graph/dag/src/critical-path.unit.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it } from "vitest"; +import { criticalPath } from "./critical-path.ts"; +import { CycleError } from "./errors.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +/** Builds a weight function from a table, so each test states its costs next to its graph. */ +function weightsFrom( + table: Readonly>, +): (id: string) => number { + const weights = new Map(Object.entries(table)); + + return (id) => { + const weight = weights.get(id); + if (weight === undefined) { + throw new Error(`No weight given for ${id}`); + } + + return weight; + }; +} + +describe("criticalPath", () => { + it("finds the unique heaviest chain and its total", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 5, c: 2, d: 1 })); + + expect(result).toEqual({ path: ["a", "b", "d"], weight: 7 }); + }); + + it("prefers a longer chain of small weights over a short heavy one when it totals more", () => { + const dag = dagOf({ a: [], x: ["a"], y: ["x"], z: ["y"], h: ["a"] }); + + const result = criticalPath( + dag, + weightsFrom({ a: 1, x: 2, y: 2, z: 2, h: 5 }), + ); + + expect(result).toEqual({ path: ["a", "x", "y", "z"], weight: 7 }); + }); + + it("follows the heavier need even when it comes later in node order", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 1, c: 5, d: 1 })); + + expect(result).toEqual({ path: ["a", "c", "d"], weight: 7 }); + }); + + it("follows the heavier need even when it is listed before a lighter, earlier node", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["c", "b"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 1, c: 5, d: 1 })); + + expect(result).toEqual({ path: ["a", "c", "d"], weight: 7 }); + }); + + it("names the offending node and weight when rejecting one", () => { + const negative = -2; + + expect(() => criticalPath(dagOf({ a: [] }), () => negative)).toThrow( + 'The weight of "a" must be a finite, non-negative number, got -2', + ); + }); + + it("breaks a tie between needs in favour of the one first in node order", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 2, c: 2, d: 1 })); + + expect(result).toEqual({ path: ["a", "b", "d"], weight: 4 }); + }); + + it("breaks a tie between needs by node order, not by the order they are listed", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["c", "b"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 2, c: 2, d: 1 })); + + expect(result.path).toEqual(["a", "b", "d"]); + }); + + it("breaks a tie between end nodes in favour of the one first in node order", () => { + const dag = dagOf({ a: [], f: ["a"], e: ["a"] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, f: 3, e: 3 })); + + expect(result).toEqual({ path: ["a", "f"], weight: 4 }); + }); + + it("returns a single node for an isolated graph member that is heaviest", () => { + const dag = dagOf({ a: [], b: ["a"], lone: [] }); + + const result = criticalPath(dag, weightsFrom({ a: 1, b: 1, lone: 9 })); + + expect(result).toEqual({ path: ["lone"], weight: 9 }); + }); + + it("runs a zero-weight chain back to its start", () => { + const dag = dagOf({ a: [], b: ["a"] }); + + const result = criticalPath(dag, weightsFrom({ a: 0, b: 0 })); + + expect(result).toEqual({ path: ["a"], weight: 0 }); + }); + + it("returns an empty path for an empty graph", () => { + expect(criticalPath(dagOf({}), () => 1)).toEqual({ path: [], weight: 0 }); + }); + + it("calls the weight function once per node, in node order", () => { + const seen: string[] = []; + criticalPath(dagOf({ a: [], b: ["a"], c: ["b"] }), (id) => { + seen.push(id); + + return 1; + }); + + expect(seen).toEqual(["a", "b", "c"]); + }); + + it("throws a CycleError when the graph is cyclic", () => { + expect(() => criticalPath(dagOf({ a: ["a"] }), () => 1)).toThrow( + CycleError, + ); + }); + + it("rejects a negative or non-finite weight", () => { + const dag = dagOf({ a: [] }); + + expect(() => criticalPath(dag, () => -1)).toThrow(RangeError); + expect(() => criticalPath(dag, () => Number.NaN)).toThrow(RangeError); + expect(() => criticalPath(dag, () => Number.POSITIVE_INFINITY)).toThrow( + RangeError, + ); + }); +}); diff --git a/packages/core/graph/dag/src/cycles.ts b/packages/core/graph/dag/src/cycles.ts new file mode 100644 index 0000000..e4d17b7 --- /dev/null +++ b/packages/core/graph/dag/src/cycles.ts @@ -0,0 +1,163 @@ +import { type Dag, type NodeId, lookup } from "./dag.ts"; +import { CycleError } from "./errors.ts"; + +interface TarjanFrame { + readonly node: Id; + next: number; +} + +/** + * Every strongly connected component of the graph, found with an explicit stack so depth is bounded by memory, not by the call stack. + */ +function stronglyConnectedComponents( + dag: Dag, +): readonly (readonly Id[])[] { + const index = new Map(); + const lowLink = new Map(); + const onStack = new Set(); + const stack: Id[] = []; + const components: Id[][] = []; + + // Pops the component rooted at `root` off the stack: every node pushed since `root`, and `root` itself. + const popComponent = (root: Id): Id[] => { + const component: Id[] = []; + for (let member = stack.pop(); member !== undefined; member = stack.pop()) { + onStack.delete(member); + component.push(member); + if (member === root) { + break; + } + } + + return component; + }; + + const visit = (node: Id): void => { + index.set(node, index.size); + lowLink.set(node, lookup(index, node)); + stack.push(node); + onStack.add(node); + }; + + for (const root of dag.nodes) { + if (index.has(root)) { + continue; + } + visit(root); + const frames: TarjanFrame[] = [{ node: root, next: 0 }]; + for ( + let frame = frames.at(-1); + frame !== undefined; + frame = frames.at(-1) + ) { + const targets = lookup(dag.needs, frame.node); + const target = targets[frame.next]; + if (target !== undefined) { + frame.next += 1; + if (!index.has(target)) { + visit(target); + frames.push({ node: target, next: 0 }); + } else if (onStack.has(target)) { + lowLink.set( + frame.node, + Math.min(lookup(lowLink, frame.node), lookup(index, target)), + ); + } + continue; + } + frames.pop(); + const parent = frames.at(-1); + if (parent !== undefined) { + lowLink.set( + parent.node, + Math.min(lookup(lowLink, parent.node), lookup(lowLink, frame.node)), + ); + } + if (lookup(lowLink, frame.node) === lookup(index, frame.node)) { + components.push(popComponent(frame.node)); + } + } + } + + return components; +} + +/** + * The shortest closed path along `needs` edges from `start` back to itself, staying inside `members`. Ties between equally short paths go to the one found first when each node's needs are read in order. + */ +function shortestCycleThrough( + dag: Dag, + start: Id, + members: ReadonlySet, +): readonly Id[] { + const parent = new Map(); + const queue: Id[] = [start]; + for (const current of queue) { + for (const target of lookup(dag.needs, current)) { + if (target === start) { + const between: Id[] = []; + for (let node = current; node !== start; node = lookup(parent, node)) { + between.push(node); + } + + return [start, ...between.reverse(), start]; + } + if (members.has(target) && !parent.has(target)) { + parent.set(target, current); + queue.push(target); + } + } + } + throw new Error( + `Invariant violated: "${String(start)}" is in a strongly connected component but no cycle passes through it`, + ); +} + +/** + * Reports every cycle in the graph: exactly one closed path per strongly connected component that contains a cycle (more than one node, or a node that needs itself). + * + * A path lists nodes such that each needs the next and the last needs the first, which is repeated at the end: `[a, b, c, a]` means a needs b, b needs c and c needs a. A self-loop is `[a, a]`. The path is a shortest cycle through the component's first node in node order, ties going to the earliest needs. Paths are ordered by the position of their first node. A component can contain more cycles than the one reported; removing the reported cycle's edges and calling again finds the rest. + * + * Returns an empty array exactly when the graph is acyclic. Iterative, so a deep graph cannot overflow the call stack. + */ +export function findCycles( + dag: Dag, +): readonly (readonly Id[])[] { + const position = new Map(dag.nodes.map((id, order) => [id, order])); + const found: { readonly first: number; readonly path: readonly Id[] }[] = []; + for (const component of stronglyConnectedComponents(dag)) { + const [only] = component; + if ( + component.length === 1 && + only !== undefined && + !lookup(dag.needs, only).includes(only) + ) { + continue; + } + const members = new Set(component); + const ranked = component + .map((id) => ({ id, order: lookup(position, id) })) + .sort((a, b) => a.order - b.order); + const [earliest] = ranked; + if (earliest === undefined) { + continue; + } + found.push({ + first: earliest.order, + path: shortestCycleThrough(dag, earliest.id, members), + }); + } + + return found.sort((a, b) => a.first - b.first).map(({ path }) => path); +} + +/** + * Throws a `CycleError` unless the graph is acyclic. + * @throws CycleError carrying `findCycles(dag)` when the graph contains a cycle. + */ +export function assertAcyclic(dag: Dag): void { + const cycles = findCycles(dag); + if (cycles.length > 0) { + throw new CycleError(cycles); + } +} diff --git a/packages/core/graph/dag/src/cycles.unit.test.ts b/packages/core/graph/dag/src/cycles.unit.test.ts new file mode 100644 index 0000000..f0a5443 --- /dev/null +++ b/packages/core/graph/dag/src/cycles.unit.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from "vitest"; +import { assertAcyclic, findCycles } from "./cycles.ts"; +import { CycleError } from "./errors.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +/** Far deeper than any call stack tolerates in a recursive walk, so an accidental recursion fails here. */ +const deepRingLength = 100_000; + +describe("findCycles", () => { + it("returns nothing for an acyclic graph", () => { + expect(findCycles(dagOf({ a: [], b: ["a"], c: ["a", "b"] }))).toEqual([]); + }); + + it("reports a cycle with its exact path, closed on its first node", () => { + expect(findCycles(dagOf({ a: ["b"], b: ["c"], c: ["a"] }))).toEqual([ + ["a", "b", "c", "a"], + ]); + }); + + it("starts the path at the component's earliest node in node order", () => { + expect(findCycles(dagOf({ x: [], c: ["b"], b: ["a"], a: ["c"] }))).toEqual([ + ["c", "b", "a", "c"], + ]); + }); + + it("reports a self-loop as a path of one node", () => { + expect(findCycles(dagOf({ a: ["a"], b: ["a"] }))).toEqual([["a", "a"]]); + }); + + it("reports one cycle per strongly connected component, ordered by first node", () => { + const cycles = findCycles( + dagOf({ p: ["q"], q: ["p"], m: [], r: ["s"], s: ["r"], t: ["p", "r"] }), + ); + + expect(cycles).toEqual([ + ["p", "q", "p"], + ["r", "s", "r"], + ]); + }); + + it("reports the shortest cycle through the component's first node", () => { + const cycles = findCycles( + dagOf({ a: ["b", "c"], b: ["d"], c: ["a"], d: ["a"] }), + ); + + expect(cycles).toEqual([["a", "c", "a"]]); + }); + + it("leaves a node that merely needs a cycle out of its path", () => { + expect(findCycles(dagOf({ a: ["b"], b: ["a"], c: ["a"] }))).toEqual([ + ["a", "b", "a"], + ]); + }); + + it("orders paths by node order even when a later component is found first", () => { + const cycles = findCycles( + dagOf({ p: ["q", "r"], q: ["p"], r: ["s"], s: ["r"] }), + ); + + expect(cycles).toEqual([ + ["p", "q", "p"], + ["r", "s", "r"], + ]); + }); + + it("finds a cycle that also needs a node already settled elsewhere", () => { + expect(findCycles(dagOf({ y: [], a: ["y", "b"], b: ["a"] }))).toEqual([ + ["a", "b", "a"], + ]); + }); + + it("keeps a node above a cycle out of the cycle's component", () => { + expect(findCycles(dagOf({ r: ["c"], c: ["d"], d: ["c"] }))).toEqual([ + ["c", "d", "c"], + ]); + }); + + it("reports a cycle once when its nodes are reached from an earlier node", () => { + expect(findCycles(dagOf({ a: ["b"], b: ["c"], c: ["b"] }))).toEqual([ + ["b", "c", "b"], + ]); + }); + + it("follows the earliest needs when two shortest cycles tie", () => { + expect( + findCycles(dagOf({ a: ["b", "c"], b: ["d"], c: ["d"], d: ["a"] })), + ).toEqual([["a", "b", "d", "a"]]); + }); + + it("ignores unknown references", () => { + expect(findCycles(dagOf({ a: ["ghost"] }))).toEqual([]); + }); + + it("handles a ring longer than the call stack", () => { + const table: Record = {}; + for (let index = 0; index < deepRingLength; index += 1) { + table[`n${String(index)}`] = [`n${String((index + 1) % deepRingLength)}`]; + } + + const [cycle] = findCycles(dagOf(table)); + + expect(cycle).toHaveLength(deepRingLength + 1); + expect(cycle?.[0]).toBe("n0"); + expect(cycle?.[deepRingLength]).toBe("n0"); + }); +}); + +describe("assertAcyclic", () => { + it("passes for an acyclic graph", () => { + expect(() => { + assertAcyclic(dagOf({ a: [], b: ["a"] })); + }).not.toThrow(); + }); + + it("throws a CycleError carrying every cycle", () => { + const thrown = (() => { + try { + assertAcyclic(dagOf({ a: ["b"], b: ["a"], c: ["c"] })); + } catch (error: unknown) { + return error; + } + + return undefined; + })(); + + expect(thrown).toBeInstanceOf(CycleError); + expect(thrown instanceof CycleError && thrown.cycles).toEqual([ + ["a", "b", "a"], + ["c", "c"], + ]); + }); +}); diff --git a/packages/core/graph/dag/src/dag.ts b/packages/core/graph/dag/src/dag.ts new file mode 100644 index 0000000..4c384b6 --- /dev/null +++ b/packages/core/graph/dag/src/dag.ts @@ -0,0 +1,102 @@ +import { UnknownNodeError } from "./errors.ts"; + +/** + * The identifier of a node. Restricted to primitives so that two ids denote the same node exactly when they are strictly equal. + */ +export type NodeId = string | number; + +/** + * A `needs` target that is not a node of the graph, reported as data instead of being absorbed. + */ +export interface UnknownReference { + /** The node whose `needs` named the missing target. */ + readonly from: Id; + /** The target that is not a node. */ + readonly missing: Id; +} + +/** + * An immutable snapshot of a dependency graph: every node, the direct `needs` of each, the inverse relation, and the references that pointed outside the node set. + * + * The type does not promise acyclicity: a graph built from arbitrary input may contain cycles, which `findCycles` reports and the operations that need acyclicity document how they treat. Nothing here may be mutated by a caller. + */ +export interface Dag { + /** Every node once, in the order the input first produced it. All other orderings in this package derive from this one. */ + readonly nodes: readonly Id[]; + /** For every node, the nodes it directly needs, without duplicates, in the order `needs` produced them, restricted to nodes of the graph. */ + readonly needs: ReadonlyMap; + /** For every node, the nodes that directly need it, in node order. The inverse of `needs`. */ + readonly dependents: ReadonlyMap; + /** Every `needs` target that is not a node, once per `{ from, missing }` pair, in node order and then `needs` order. These are not edges of the graph. */ + readonly unknownReferences: readonly UnknownReference[]; +} + +/** + * Reads a value keyed by a node id out of a map built over a graph's nodes. + * @throws UnknownNodeError when the id is not a node of that graph. + */ +export function lookup( + map: ReadonlyMap, + id: Id, +): Value { + const value = map.get(id); + if (value === undefined) { + throw new UnknownNodeError(id); + } + + return value; +} + +/** + * Assembles a graph from nodes and already-resolved direct needs, deriving the inverse relation. Internal: every needs target must be a node. + */ +export function assembleDag( + nodes: readonly Id[], + needs: ReadonlyMap, + unknownReferences: readonly UnknownReference[], +): Dag { + const dependents = new Map(nodes.map((id) => [id, []])); + for (const id of nodes) { + for (const target of lookup(needs, id)) { + lookup(dependents, target).push(id); + } + } + + return { nodes, needs, dependents, unknownReferences }; +} + +/** + * Builds a graph once from a node set and a `needs` accessor. + * + * `nodes` is iterated once and duplicates are dropped, keeping the first occurrence. `needs` is called exactly once per distinct node, in node order, and its result is iterated once. A target that is not a node is recorded in `unknownReferences` and is not an edge. A node may need itself; that is a cycle of one, reported by `findCycles`. + * + * Never throws for malformed structure: cycles and unknown references are data on the result and in the analyses. + * @param nodes - Every node of the graph. + * @param needs - The direct needs of a node: the nodes that must come before it. + */ +export function buildDag( + nodes: Readonly>, + needs: (id: Id) => Iterable, +): Dag { + const ordered = [...new Set(nodes)]; + const known = new Set(ordered); + const resolved = new Map(); + const unknownReferences: UnknownReference[] = []; + for (const id of ordered) { + const direct = new Set(); + const missing = new Set(); + for (const target of needs(id)) { + if (known.has(target)) { + direct.add(target); + } else { + missing.add(target); + } + } + resolved.set(id, [...direct]); + for (const target of missing) { + unknownReferences.push({ from: id, missing: target }); + } + } + + return assembleDag(ordered, resolved, unknownReferences); +} diff --git a/packages/core/graph/dag/src/dag.unit.test.ts b/packages/core/graph/dag/src/dag.unit.test.ts new file mode 100644 index 0000000..817340e --- /dev/null +++ b/packages/core/graph/dag/src/dag.unit.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "vitest"; +import { buildDag } from "./dag.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +describe("buildDag", () => { + it("keeps node order and exposes needs and their inverse", () => { + const dag = dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"] }); + + expect(dag.nodes).toEqual(["a", "b", "c", "d"]); + expect(dag.needs.get("d")).toEqual(["b", "c"]); + expect(dag.dependents.get("a")).toEqual(["b", "c"]); + expect(dag.dependents.get("d")).toEqual([]); + expect(dag.unknownReferences).toEqual([]); + }); + + it("drops duplicate nodes and duplicate needs, keeping first occurrences", () => { + const dag = buildDag(["a", "b", "a"], (id) => + id === "b" ? ["a", "a"] : [], + ); + + expect(dag.nodes).toEqual(["a", "b"]); + expect(dag.needs.get("b")).toEqual(["a"]); + expect(dag.dependents.get("a")).toEqual(["b"]); + }); + + it("reports needs that are not nodes as data and leaves them out of the edges", () => { + const dag = dagOf({ a: ["ghost"], b: ["a", "phantom", "ghost", "ghost"] }); + + expect(dag.unknownReferences).toEqual([ + { from: "a", missing: "ghost" }, + { from: "b", missing: "phantom" }, + { from: "b", missing: "ghost" }, + ]); + expect(dag.needs.get("a")).toEqual([]); + expect(dag.needs.get("b")).toEqual(["a"]); + }); + + it("keeps a node that needs itself as an edge", () => { + const dag = dagOf({ a: ["a"] }); + + expect(dag.needs.get("a")).toEqual(["a"]); + expect(dag.dependents.get("a")).toEqual(["a"]); + }); + + it("calls the needs accessor exactly once per distinct node, in node order", () => { + const calls: string[] = []; + buildDag(["x", "y", "x"], (id) => { + calls.push(id); + + return []; + }); + + expect(calls).toEqual(["x", "y"]); + }); + + it("accepts numeric ids", () => { + const first = 1; + const second = 2; + const third = 3; + const dag = buildDag([first, second, third], (id) => + id === third ? [first, second] : [], + ); + + expect(dag.needs.get(third)).toEqual([first, second]); + }); +}); diff --git a/packages/core/graph/dag/src/errors.ts b/packages/core/graph/dag/src/errors.ts new file mode 100644 index 0000000..edc4789 --- /dev/null +++ b/packages/core/graph/dag/src/errors.ts @@ -0,0 +1,41 @@ +import type { NodeId } from "./dag.ts"; + +/** + * Thrown when an operation that is only defined on an acyclic graph is given one that contains a cycle. + */ +export class CycleError extends Error { + /** + * One closed path per strongly connected component that contains a cycle, exactly as `findCycles` reports them. + */ + readonly cycles: readonly (readonly Id[])[]; + + /** + * @param cycles - The cycles found, as returned by `findCycles`. Must not be empty. + */ + constructor(cycles: readonly (readonly Id[])[]) { + super( + `The graph contains a cycle: ${cycles.map((path) => path.join(" -> ")).join("; ")}`, + ); + this.name = "CycleError"; + this.cycles = cycles; + } +} + +/** + * Thrown when an operation is asked about an id that is not a node of the graph it was given. + */ +export class UnknownNodeError extends Error { + /** + * The id that is not a node of the graph. + */ + readonly id: Id; + + /** + * @param id - The id that is not a node of the graph. + */ + constructor(id: Id) { + super(`"${String(id)}" is not a node of the graph`); + this.name = "UnknownNodeError"; + this.id = id; + } +} diff --git a/packages/core/graph/dag/src/errors.unit.test.ts b/packages/core/graph/dag/src/errors.unit.test.ts new file mode 100644 index 0000000..548fb1d --- /dev/null +++ b/packages/core/graph/dag/src/errors.unit.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { CycleError, UnknownNodeError } from "./errors.ts"; + +describe("CycleError", () => { + it("carries the cycles and names them in its message", () => { + const error = new CycleError([ + ["a", "b", "a"], + ["c", "c"], + ]); + + expect(error.name).toBe("CycleError"); + expect(error.cycles).toEqual([ + ["a", "b", "a"], + ["c", "c"], + ]); + expect(error.message).toBe( + "The graph contains a cycle: a -> b -> a; c -> c", + ); + }); +}); + +describe("UnknownNodeError", () => { + it("carries the id and names it in its message", () => { + const error = new UnknownNodeError("ghost"); + + expect(error.name).toBe("UnknownNodeError"); + expect(error.id).toBe("ghost"); + expect(error.message).toBe('"ghost" is not a node of the graph'); + }); +}); diff --git a/packages/core/graph/dag/src/index.ts b/packages/core/graph/dag/src/index.ts new file mode 100644 index 0000000..f4f59e4 --- /dev/null +++ b/packages/core/graph/dag/src/index.ts @@ -0,0 +1,12 @@ +export { + buildDag, + type Dag, + type NodeId, + type UnknownReference, +} from "./dag.ts"; +export { CycleError, UnknownNodeError } from "./errors.ts"; +export { topologicalLayers, type TopologicalLayers } from "./layers.ts"; +export { ancestorsOf, descendantsOf } from "./reachability.ts"; +export { findCycles } from "./cycles.ts"; +export { transitiveReduction } from "./reduction.ts"; +export { criticalPath, type CriticalPath } from "./critical-path.ts"; diff --git a/packages/core/graph/dag/src/layers.ts b/packages/core/graph/dag/src/layers.ts new file mode 100644 index 0000000..b66ccb3 --- /dev/null +++ b/packages/core/graph/dag/src/layers.ts @@ -0,0 +1,54 @@ +import { type Dag, type NodeId, lookup } from "./dag.ts"; + +/** + * The result of layering a graph by dependency depth. + */ +export interface TopologicalLayers { + /** Each layered node's level: the length of the longest `needs` chain below it, so a node with no needs is 0 and every other node is one more than its highest-level need. */ + readonly levels: ReadonlyMap; + /** Nodes grouped by level, index 0 first. Within a layer, nodes are in node order. Every node of a layer needs only nodes of earlier layers, and nothing in one layer needs anything in the same layer. */ + readonly layers: readonly (readonly Id[])[]; + /** The nodes that have no level because they lie on a cycle or transitively need a node that does, in node order. Empty exactly when the graph is acyclic. */ + readonly unlayered: readonly Id[]; +} + +/** + * Layers the graph by the longest `needs` chain below each node, so everything in one layer can run once the earlier layers are done. + * + * Defined on the acyclic part of the graph: a node on a cycle, or one that needs such a node directly or transitively, has no finite chain length and is reported in `unlayered` rather than being given a level. Every other node is layered exactly as in an acyclic graph. Unknown references are not edges and contribute nothing to a level. Iterative (Kahn's algorithm), so depth cannot overflow the call stack. + */ +export function topologicalLayers( + dag: Dag, +): TopologicalLayers { + const depth = new Map(dag.nodes.map((id) => [id, 0])); + const waitingOn = new Map( + dag.nodes.map((id) => [id, lookup(dag.needs, id).length]), + ); + const ready: Id[] = dag.nodes.filter((id) => lookup(waitingOn, id) === 0); + for (const current of ready) { + const above = lookup(depth, current) + 1; + for (const dependent of lookup(dag.dependents, current)) { + depth.set(dependent, Math.max(lookup(depth, dependent), above)); + const remaining = lookup(waitingOn, dependent) - 1; + waitingOn.set(dependent, remaining); + if (remaining === 0) { + ready.push(dependent); + } + } + } + + const levels = new Map(); + const layers: Id[][] = []; + const unlayered: Id[] = []; + for (const id of dag.nodes) { + if (lookup(waitingOn, id) !== 0) { + unlayered.push(id); + continue; + } + const level = lookup(depth, id); + levels.set(id, level); + (layers[level] ??= []).push(id); + } + + return { levels, layers, unlayered }; +} diff --git a/packages/core/graph/dag/src/layers.unit.test.ts b/packages/core/graph/dag/src/layers.unit.test.ts new file mode 100644 index 0000000..adbf6d7 --- /dev/null +++ b/packages/core/graph/dag/src/layers.unit.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { topologicalLayers } from "./layers.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +/** Far deeper than any call stack tolerates in a recursive walk, so an accidental recursion fails here. */ +const deepChainLength = 100_000; + +describe("topologicalLayers", () => { + it("layers a diamond by longest chain", () => { + const result = topologicalLayers( + dagOf({ d: ["b", "c"], b: ["a"], c: ["a"], a: [] }), + ); + + expect(result.layers).toEqual([["a"], ["b", "c"], ["d"]]); + expect(Object.fromEntries(result.levels)).toEqual({ + a: 0, + b: 1, + c: 1, + d: 2, + }); + expect(result.unlayered).toEqual([]); + }); + + it("layers a node that needs a diamond", () => { + const result = topologicalLayers( + dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"], e: ["d"] }), + ); + + expect(result.layers).toEqual([["a"], ["b", "c"], ["d"], ["e"]]); + expect(result.unlayered).toEqual([]); + }); + + it("places a node above its deepest need, not its shallowest", () => { + const result = topologicalLayers(dagOf({ a: [], b: ["a"], c: ["b", "a"] })); + + expect(result.levels.get("c")).toBe(2); + expect(result.layers).toEqual([["a"], ["b"], ["c"]]); + }); + + it("puts an isolated node in layer 0", () => { + const result = topologicalLayers(dagOf({ a: [], lone: [] })); + + expect(result.layers).toEqual([["a", "lone"]]); + }); + + it("ignores unknown references when computing a level", () => { + const result = topologicalLayers(dagOf({ a: ["ghost"], b: ["a"] })); + + expect(result.levels.get("a")).toBe(0); + expect(result.levels.get("b")).toBe(1); + }); + + it("reports nodes on or above a cycle as unlayered and layers the rest", () => { + const result = topologicalLayers( + dagOf({ w: [], x: ["y"], y: ["x"], z: ["x"], v: ["w"] }), + ); + + expect(result.layers).toEqual([["w"], ["v"]]); + expect(result.unlayered).toEqual(["x", "y", "z"]); + expect(result.levels.has("x")).toBe(false); + }); + + it("reports a self-loop as unlayered", () => { + const result = topologicalLayers(dagOf({ a: ["a"] })); + + expect(result.layers).toEqual([]); + expect(result.unlayered).toEqual(["a"]); + }); + + it("handles a chain deeper than the call stack", () => { + const table: Record = {}; + for (let index = 0; index < deepChainLength; index += 1) { + table[`n${String(index)}`] = index === 0 ? [] : [`n${String(index - 1)}`]; + } + + const result = topologicalLayers(dagOf(table)); + + expect(result.layers).toHaveLength(deepChainLength); + expect(result.levels.get(`n${String(deepChainLength - 1)}`)).toBe( + deepChainLength - 1, + ); + }); +}); diff --git a/packages/core/graph/dag/src/reachability.ts b/packages/core/graph/dag/src/reachability.ts new file mode 100644 index 0000000..6da9b83 --- /dev/null +++ b/packages/core/graph/dag/src/reachability.ts @@ -0,0 +1,48 @@ +import { type Dag, type NodeId, lookup } from "./dag.ts"; + +/** + * Every node reachable from `from` by following `edges` one or more times, in node order. + */ +function reachable( + dag: Dag, + edges: ReadonlyMap, + from: Id, +): readonly Id[] { + const found = new Set(); + const pending = [...lookup(edges, from)]; + for (let node = pending.pop(); node !== undefined; node = pending.pop()) { + if (found.has(node)) { + continue; + } + found.add(node); + pending.push(...lookup(edges, node)); + } + + return dag.nodes.filter((id) => found.has(id)); +} + +/** + * Every node `id` depends on, directly or through other nodes: the transitive closure of `needs` from `id`, in node order. + * + * `id` itself is included exactly when it lies on a cycle, since it then depends on itself. Unknown references are not edges. Iterative, so depth cannot overflow the call stack. + * @throws UnknownNodeError when `id` is not a node of the graph. + */ +export function ancestorsOf( + dag: Dag, + id: Id, +): readonly Id[] { + return reachable(dag, dag.needs, id); +} + +/** + * Every node that depends on `id`, directly or through other nodes: the transitive closure of the inverse of `needs` from `id`, in node order. + * + * `id` itself is included exactly when it lies on a cycle. Iterative, so depth cannot overflow the call stack. + * @throws UnknownNodeError when `id` is not a node of the graph. + */ +export function descendantsOf( + dag: Dag, + id: Id, +): readonly Id[] { + return reachable(dag, dag.dependents, id); +} diff --git a/packages/core/graph/dag/src/reachability.unit.test.ts b/packages/core/graph/dag/src/reachability.unit.test.ts new file mode 100644 index 0000000..eb7091a --- /dev/null +++ b/packages/core/graph/dag/src/reachability.unit.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest"; +import { ancestorsOf, descendantsOf } from "./reachability.ts"; +import { UnknownNodeError } from "./errors.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +const diamond = dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"], e: [] }); + +describe("ancestorsOf", () => { + it("returns everything a node depends on, transitively, in node order", () => { + expect(ancestorsOf(diamond, "d")).toEqual(["a", "b", "c"]); + expect(ancestorsOf(diamond, "b")).toEqual(["a"]); + }); + + it("returns nothing for a node with no needs and for an isolated node", () => { + expect(ancestorsOf(diamond, "a")).toEqual([]); + expect(ancestorsOf(diamond, "e")).toEqual([]); + }); + + it("includes the node itself exactly when it is on a cycle", () => { + const cyclic = dagOf({ a: ["b"], b: ["a"], c: ["a"] }); + + expect(ancestorsOf(cyclic, "a")).toEqual(["a", "b"]); + expect(ancestorsOf(cyclic, "c")).toEqual(["a", "b"]); + }); + + it("throws for an id that is not a node", () => { + expect(() => ancestorsOf(diamond, "ghost")).toThrow(UnknownNodeError); + }); +}); + +describe("descendantsOf", () => { + it("returns everything that depends on a node, transitively, in node order", () => { + expect(descendantsOf(diamond, "a")).toEqual(["b", "c", "d"]); + expect(descendantsOf(diamond, "c")).toEqual(["d"]); + }); + + it("returns nothing for a leaf dependent and for an isolated node", () => { + expect(descendantsOf(diamond, "d")).toEqual([]); + expect(descendantsOf(diamond, "e")).toEqual([]); + }); + + it("includes a self-looping node itself", () => { + expect(descendantsOf(dagOf({ a: ["a"] }), "a")).toEqual(["a"]); + }); + + it("throws for an id that is not a node", () => { + expect(() => descendantsOf(diamond, "ghost")).toThrow(UnknownNodeError); + }); +}); diff --git a/packages/core/graph/dag/src/reduction.ts b/packages/core/graph/dag/src/reduction.ts new file mode 100644 index 0000000..88bd15d --- /dev/null +++ b/packages/core/graph/dag/src/reduction.ts @@ -0,0 +1,43 @@ +import { assertAcyclic } from "./cycles.ts"; +import { type Dag, type NodeId, assembleDag, lookup } from "./dag.ts"; +import { topologicalLayers } from "./layers.ts"; + +/** + * The transitive reduction: the same nodes with every redundant edge removed, so a node keeps a direct need only when no other need of it already depends on that node, transitively. + * + * Defined for acyclic graphs, where it is unique and has exactly the same ancestors and descendants as the input. Each node's remaining needs keep the order they had. Unknown references are carried over unchanged; they are not edges and take no part in reduction. + * + * Builds each node's full set of ancestors, so memory grows with the size of the transitive closure. + * @throws CycleError when the graph contains a cycle: with one the reduction is not unique, so nothing is guessed. + */ +export function transitiveReduction(dag: Dag): Dag { + assertAcyclic(dag); + const ancestors = new Map>(); + for (const layer of topologicalLayers(dag).layers) { + for (const id of layer) { + const closure = new Set(); + for (const need of lookup(dag.needs, id)) { + closure.add(need); + for (const further of lookup(ancestors, need)) { + closure.add(further); + } + } + ancestors.set(id, closure); + } + } + + // In an acyclic graph no node is its own ancestor, so a need never makes itself redundant. + const reduced = new Map(); + for (const id of dag.nodes) { + const direct = lookup(dag.needs, id); + reduced.set( + id, + direct.filter( + (candidate) => + !direct.some((other) => lookup(ancestors, other).has(candidate)), + ), + ); + } + + return assembleDag(dag.nodes, reduced, dag.unknownReferences); +} diff --git a/packages/core/graph/dag/src/reduction.unit.test.ts b/packages/core/graph/dag/src/reduction.unit.test.ts new file mode 100644 index 0000000..01037f1 --- /dev/null +++ b/packages/core/graph/dag/src/reduction.unit.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from "vitest"; +import { CycleError } from "./errors.ts"; +import { transitiveReduction } from "./reduction.ts"; +import { dagOf } from "./test-support/dag-of.ts"; + +describe("transitiveReduction", () => { + it("drops the shortcut edge across a chain", () => { + const reduced = transitiveReduction( + dagOf({ a: [], b: ["a"], c: ["b", "a"] }), + ); + + expect(reduced.needs.get("c")).toEqual(["b"]); + expect(reduced.needs.get("b")).toEqual(["a"]); + expect(reduced.dependents.get("a")).toEqual(["b"]); + }); + + it("removes the edge that a diamond already implies and keeps the diamond", () => { + const reduced = transitiveReduction( + dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c", "a"] }), + ); + + expect(reduced.needs.get("d")).toEqual(["b", "c"]); + expect(reduced.needs.get("b")).toEqual(["a"]); + expect(reduced.needs.get("c")).toEqual(["a"]); + }); + + it("leaves a graph with no redundant edges unchanged", () => { + const reduced = transitiveReduction( + dagOf({ a: [], b: ["a"], c: ["a"], d: ["b", "c"], e: [] }), + ); + + expect([...reduced.needs]).toEqual([ + ["a", []], + ["b", ["a"]], + ["c", ["a"]], + ["d", ["b", "c"]], + ["e", []], + ]); + }); + + it("removes a redundancy that spans several hops", () => { + const reduced = transitiveReduction( + dagOf({ a: [], b: ["a"], c: ["b"], d: ["c", "a"] }), + ); + + expect(reduced.needs.get("d")).toEqual(["c"]); + }); + + it("carries unknown references over unchanged", () => { + const reduced = transitiveReduction(dagOf({ a: ["ghost"], b: ["a"] })); + + expect(reduced.unknownReferences).toEqual([ + { from: "a", missing: "ghost" }, + ]); + }); + + it("throws a CycleError naming the cycle when the graph is cyclic", () => { + const attempt = (): unknown => + transitiveReduction(dagOf({ a: ["b"], b: ["a"] })); + + expect(attempt).toThrow(CycleError); + expect(attempt).toThrow("a -> b -> a"); + }); +}); diff --git a/packages/core/graph/dag/src/test-support/dag-of.ts b/packages/core/graph/dag/src/test-support/dag-of.ts new file mode 100644 index 0000000..df08d9e --- /dev/null +++ b/packages/core/graph/dag/src/test-support/dag-of.ts @@ -0,0 +1,12 @@ +import { type Dag, buildDag, lookup } from "../dag.ts"; + +/** + * Builds a graph from a table of node to needs, taking node order from the table's key order. A need that is not a key becomes an unknown reference. + */ +export function dagOf( + table: Readonly>, +): Dag { + const needs = new Map(Object.entries(table)); + + return buildDag(needs.keys(), (id) => lookup(needs, id)); +} diff --git a/packages/core/graph/dag/stryker.config.ts b/packages/core/graph/dag/stryker.config.ts new file mode 100644 index 0000000..6429b27 --- /dev/null +++ b/packages/core/graph/dag/stryker.config.ts @@ -0,0 +1,3 @@ +import { packageStrykerConfig } from "../../../../stryker.shared.ts"; + +export default packageStrykerConfig(); diff --git a/packages/core/graph/dag/test/package.smoke.test.ts b/packages/core/graph/dag/test/package.smoke.test.ts new file mode 100644 index 0000000..5e93d31 --- /dev/null +++ b/packages/core/graph/dag/test/package.smoke.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from "vitest"; +// Deliberately imports the built dist/, not src/: this is the check that catches a wrong exports map or a build misconfiguration that a unit test importing straight from src/ never touches. +import { + buildDag, + criticalPath, + findCycles, + topologicalLayers, +} from "../dist/index.js"; + +test("the built package analyses a graph end to end", () => { + const dag = buildDag(["a", "b", "c"], (id) => { + if (id === "b") return ["a"]; + if (id === "c") return ["b"]; + + return []; + }); + + expect(topologicalLayers(dag).layers).toEqual([["a"], ["b"], ["c"]]); + expect(findCycles(dag)).toEqual([]); + expect(criticalPath(dag, () => 1)).toEqual({ + path: ["a", "b", "c"], + weight: 3, + }); +}); diff --git a/packages/core/graph/dag/tsconfig.build.json b/packages/core/graph/dag/tsconfig.build.json new file mode 100644 index 0000000..facc9a8 --- /dev/null +++ b/packages/core/graph/dag/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "noEmit": false, + "declaration": true, + "declarationMap": false, + "rootDir": "src", + "outDir": "dist", + "rewriteRelativeImportExtensions": true + }, + "references": [] +} diff --git a/packages/core/graph/dag/tsconfig.json b/packages/core/graph/dag/tsconfig.json new file mode 100644 index 0000000..2d77b1a --- /dev/null +++ b/packages/core/graph/dag/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024"], + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/graph/dag/tsconfig.node.json b/packages/core/graph/dag/tsconfig.node.json new file mode 100644 index 0000000..8a460f4 --- /dev/null +++ b/packages/core/graph/dag/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024"], + "types": ["node"] + }, + "include": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts", "*.config.ts"] +} diff --git a/packages/core/graph/dag/vitest.config.ts b/packages/core/graph/dag/vitest.config.ts new file mode 100644 index 0000000..01b9391 --- /dev/null +++ b/packages/core/graph/dag/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + projects: [ + { + extends: true, + test: { name: "unit", include: ["src/**/*.test.ts"] }, + }, + { + extends: true, + test: { name: "smoke", include: ["test/package.smoke.test.ts"] }, + }, + ], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c1f105..d2d4374 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,6 +205,9 @@ importers: '@semantic-release/release-notes-generator': specifier: 14.1.1 version: 14.1.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + '@stryker-mutator/api': + specifier: 10.0.0 + version: 10.0.0 '@types/node': specifier: 26.6.4 version: 26.6.4 @@ -257,8 +260,41 @@ importers: specifier: 8.71.0 version: 8.71.0(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitest: - specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 4.1.11 + version: 4.1.11(@types/node@26.6.4)(@vitest/coverage-v8@4.1.11)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + + packages/core/graph/dag: + devDependencies: + '@arethetypeswrong/cli': + specifier: 0.18.5 + version: 0.18.5 + '@stryker-mutator/core': + specifier: 10.0.0 + version: 10.0.0(@types/node@26.6.4) + '@stryker-mutator/typescript-checker': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(typescript@6.0.3) + '@stryker-mutator/vitest-runner': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(vitest@4.1.11) + '@types/node': + specifier: 26.6.4 + version: 26.6.4 + '@vitest/coverage-v8': + specifier: 4.1.11 + version: 4.1.11(vitest@4.1.11) + eslint: + specifier: 10.12.0 + version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) + publint: + specifier: 0.3.25 + version: 0.3.25 + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: 4.1.11 + version: 4.1.11(@types/node@26.6.4)(@vitest/coverage-v8@4.1.11)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages: @@ -274,31 +310,231 @@ packages: '@actions/io@3.0.2': resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} + '@andrewbranch/untar.js@1.0.4': + resolution: {integrity: sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==} + + '@arethetypeswrong/cli@0.18.5': + resolution: {integrity: sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==} + engines: {node: '>=20'} + hasBin: true + + '@arethetypeswrong/core@0.18.5': + resolution: {integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==} + engines: {node: '>=20'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@8.0.6': + resolution: {integrity: sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/compat-data@8.0.5': + resolution: {integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/core@8.0.6': + resolution: {integrity: sha512-5zwYt1V4ji3mXKKRIAkohSJ29WepxNwxQi+7y7P5AcVPh1dcxnZBU2cfxHnPwS+u9+lZnXPsv30TQaF2xg+Tfg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/generator@8.0.6': + resolution: {integrity: sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-annotate-as-pure@8.0.0': + resolution: {integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-compilation-targets@8.0.6': + resolution: {integrity: sha512-HTrtZ4+iA0+kzdbCgvdSk1U4pEl3BvpGnlUbualCGNbrVhhQvhgBzurTUmqpWyvyHbWYWaIfdx2mOlL9LJjsew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-create-class-features-plugin@8.0.6': + resolution: {integrity: sha512-Hy7wG2Pk1LnAkFMq0CbBk789XMTYVzYGxR7tKgk+TOpZckQRME3Tdou4mMT/8DFjJFxFZ8R6UsS4DR/duBKlVA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-globals@8.0.6': + resolution: {integrity: sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-member-expression-to-functions@8.0.5': + resolution: {integrity: sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-module-imports@8.0.0': + resolution: {integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-module-transforms@8.0.6': + resolution: {integrity: sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-optimise-call-expression@8.0.0': + resolution: {integrity: sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-plugin-utils@8.0.1': + resolution: {integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-replace-supers@8.0.1': + resolution: {integrity: sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + resolution: {integrity: sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.6': + resolution: {integrity: sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.6': + resolution: {integrity: sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-option@8.0.0': + resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helpers@8.0.5': + resolution: {integrity: sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/parser@7.29.9': resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.6': + resolution: {integrity: sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/plugin-proposal-decorators@8.0.2': + resolution: {integrity: sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-decorators@8.0.1': + resolution: {integrity: sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-jsx@8.0.1': + resolution: {integrity: sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-typescript@8.0.3': + resolution: {integrity: sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-destructuring@8.0.5': + resolution: {integrity: sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-explicit-resource-management@8.0.5': + resolution: {integrity: sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-modules-commonjs@8.0.1': + resolution: {integrity: sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-display-name@8.0.1': + resolution: {integrity: sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-jsx-development@8.0.1': + resolution: {integrity: sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-jsx@8.0.1': + resolution: {integrity: sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-pure-annotations@8.0.1': + resolution: {integrity: sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-typescript@8.0.6': + resolution: {integrity: sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/preset-react@8.0.1': + resolution: {integrity: sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/preset-typescript@8.0.1': + resolution: {integrity: sha512-qrPhQIN1NLrPmzgazF9XKQqXrOcp/WJly+K+6ReFonn24FZqRJO7clxOJo6Ni75L+2vAqI3cHVU2OJLBxoPp5A==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/template@8.0.0': + resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/traverse@8.0.6': + resolution: {integrity: sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/types@7.29.8': resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.6': + resolution: {integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==} + engines: {node: ^22.18.0 || >=24.11.0} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} + '@cacheable/memory@2.2.0': resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} @@ -515,6 +751,143 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@2.0.8': + resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.5': + resolution: {integrity: sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.3.2': + resolution: {integrity: sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.3': + resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.3.3': + resolution: {integrity: sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.5': + resolution: {integrity: sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.5': + resolution: {integrity: sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.9': + resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.6': + resolution: {integrity: sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.2.3': + resolution: {integrity: sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.2.2': + resolution: {integrity: sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.7.2': + resolution: {integrity: sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.5': + resolution: {integrity: sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.3.3': + resolution: {integrity: sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.5': + resolution: {integrity: sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.1.1': + resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.4.0-beta.0': + resolution: {integrity: sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -534,6 +907,9 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} + '@microsoft/tsdoc-config@0.18.3': resolution: {integrity: sha512-0I0rGXo1eJziw3EEErkDS9p/0RzBcyOiV+zbSDqOZTeURmhtut+WIvpih0LM5g+ZpZ9SyFxDfkhAcl/r8e2zGw==} @@ -855,6 +1231,10 @@ packages: resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} + '@publint/pack@0.1.7': + resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} + engines: {node: '>=18'} + '@rolldown/binding-android-arm-eabi@1.2.12': resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1021,6 +1401,39 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@stryker-mutator/api@10.0.0': + resolution: {integrity: sha512-ZtAJ0ZT3MVRCWJTBE2h90XB/6E+4lifHYtcTyNG6nU2nLekPgTo4gD5esjX6Okxo1b/JB4jJzyxYB54fwKAoJw==} + engines: {node: '>=22.0.0'} + + '@stryker-mutator/core@10.0.0': + resolution: {integrity: sha512-ZvMsRyaXQQ5e6Thcid9pkuODv6Fn9E3nrBQJUap+hcJuGJ4unm26afo3m6YKSjn8kinyxJ/3TXf0cTWRDaTxVw==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@stryker-mutator/instrumenter@10.0.0': + resolution: {integrity: sha512-B7Wmn1KlEWyFeOz6D6oGvQGRfi5Xw3VemG6dEKvFQp4qLvxD9Mf4kcZghfxffgnYwXd3bFgqXsJ+ZGlhdfIOrQ==} + engines: {node: '>=22.0.0'} + + '@stryker-mutator/typescript-checker@10.0.0': + resolution: {integrity: sha512-GLvC0LVd8RJ5SxR3eFcgb7DFKmw07DYtPXs942z6Qh7l6p7YBnFyd7lPw7nvA9UKEYDd22embrFmLlyC1XJjXw==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@stryker-mutator/core': 10.0.0 + typescript: '>=3.6' + + '@stryker-mutator/util@10.0.0': + resolution: {integrity: sha512-LzOpHiJaCp2ABQgnPMlrQQcsK43bd5Vo/2FGL78aN62yDoeRQ+4j3tzeuXxK5OAHdC3fUz6TDoy4IsoAuLAd3w==} + + '@stryker-mutator/vitest-runner@10.0.0': + resolution: {integrity: sha512-SHK2/vfvRUpiz7jXPnQMBnr6zLdm69DK03Mo5mPhaZWcRSygrKUqYsPqWsXsK+5ySHzlMTfCyFK5NQ/X9sJFFw==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@stryker-mutator/core': 10.0.0 + vitest: '>=2.0.0' + '@stylistic/eslint-plugin@6.0.0-beta.6': resolution: {integrity: sha512-DqDQZSrV/0AZ/4qLxaQUtNxafk3qWfeG5xOHx64ELjHWSHt2GtELZR6ClrNVvQwo/d13Atf8Hrh/s/ZnuJk57w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -1081,9 +1494,15 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/gensync@1.0.5': + resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1238,25 +1657,20 @@ packages: resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-v8@5.0.3': - resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} + '@vitest/coverage-v8@4.1.11': + resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} peerDependencies: - '@vitest/browser': 5.0.3 - vitest: 5.0.3 + '@vitest/browser': 4.1.11 + vitest: 4.1.11 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/istanbul-lib-coverage@1.0.2': - resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} - engines: {node: '>=22'} - - '@vitest/istanbul-lib-report@1.0.2': - resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} - engines: {node: '>=22'} + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - '@vitest/mocker@5.0.3': - resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1266,8 +1680,20 @@ packages: vite: optional: true - '@vitest/spy@5.0.3': - resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1296,6 +1722,10 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + angular-html-parser@10.11.0: + resolution: {integrity: sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==} + engines: {node: '>= 14'} + ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -1351,6 +1781,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.11.27: + resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} + engines: {node: '>=6.0.0'} + hasBin: true + before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} @@ -1365,13 +1800,29 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + cacheable@2.5.0: resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1398,6 +1849,12 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + clean-stack@5.3.0: resolution: {integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==} engines: {node: '>=14.16'} @@ -1411,6 +1868,10 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -1431,6 +1892,14 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -1488,6 +1957,9 @@ packages: resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} engines: {node: '>=12'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1539,6 +2011,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1546,6 +2021,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1558,9 +2036,16 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + electron-to-chromium@1.5.444: + resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1589,6 +2074,10 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -1596,6 +2085,10 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-toolkit@1.52.0: resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} @@ -1740,9 +2233,18 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.8: resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} @@ -1758,6 +2260,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} engines: {node: '>=4'} @@ -1795,6 +2300,10 @@ packages: flatted@3.4.4: resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + flru@1.0.2: + resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} + engines: {node: '>=6'} + format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -1820,6 +2329,10 @@ packages: resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} engines: {node: '>=18'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1828,6 +2341,14 @@ packages: resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -1861,6 +2382,10 @@ packages: resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -1880,6 +2405,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hashery@1.5.1: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} @@ -1912,6 +2441,9 @@ packages: html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-proxy-agent@9.1.0: resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} engines: {node: '>= 20'} @@ -1933,6 +2465,10 @@ packages: engines: {node: '>=18'} hasBin: true + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2027,6 +2563,18 @@ packages: resolution: {integrity: sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==} engines: {node: ^18.17 || >=20.6.1} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + java-properties@1.0.2: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} @@ -2042,6 +2590,9 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-md4@0.3.2: + resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -2056,12 +2607,20 @@ packages: resolution: {integrity: sha512-eFY7Rlscvv/BpY6UsVsjKwMhu1tJO/F4U/vC9g3yeRy/JqszuO1UP6Vdq+85G+3FP6fEw/sWR8a5ftPFY86csw==} engines: {node: ^22.22.2 || >=24.15.0} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-rpc-2.0@1.8.1: + resolution: {integrity: sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -2074,6 +2633,11 @@ packages: json-with-bigint@3.5.12: resolution: {integrity: sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} @@ -2196,6 +2760,9 @@ packages: lodash.escaperegexp@4.1.2: resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -2215,8 +2782,8 @@ packages: resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} engines: {node: 20 || >=22} - magic-string@1.4.2: - resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.5.5: resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} @@ -2225,6 +2792,10 @@ packages: resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} engines: {node: '>=18'} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2239,6 +2810,15 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -2391,6 +2971,9 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -2401,9 +2984,30 @@ packages: module-replacements@2.11.0: resolution: {integrity: sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mutation-server-protocol@0.4.1: + resolution: {integrity: sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==} + engines: {node: '>=18'} + + mutation-testing-elements@3.8.4: + resolution: {integrity: sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==} + + mutation-testing-metrics@3.8.4: + resolution: {integrity: sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==} + + mutation-testing-report-schema@3.8.4: + resolution: {integrity: sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==} + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -2425,6 +3029,10 @@ packages: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} + normalize-package-data@6.0.2: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} @@ -2523,6 +3131,10 @@ packages: object-deep-merge@2.0.1: resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} @@ -2647,6 +3259,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2690,6 +3305,10 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -2702,6 +3321,11 @@ packages: kerberos: optional: true + publint@0.3.25: + resolution: {integrity: sha512-WMMbn/d7JqRlfXQL/JUI4n5skC8cJ5WVr8/wT5mmiYWkUdENkZr1I5rgs2+dQPVRgoJd+JRYtB46MTsuDylw9g==} + engines: {node: '>=18'} + hasBin: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2710,6 +3334,10 @@ packages: resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -2770,9 +3398,19 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semantic-release@25.0.9: resolution: {integrity: sha512-bxve7csK0/Txr++CkfrmV+X1r4jqiSOw2WsSad9E2S68R+ZfLBwDn8IceM8WfiOmKQIHgsQc1cNA8Dzg7U75pg==} engines: {node: ^22.14.0 || >= 24.10.0} @@ -2795,6 +3433,25 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2819,6 +3476,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + spawn-error-forwarder@1.0.0: resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} @@ -2840,6 +3501,9 @@ packages: split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} @@ -2988,9 +3652,8 @@ packages: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} - tinybench@6.2.0: - resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} - engines: {node: '>=20.0.0'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} @@ -3016,6 +3679,10 @@ packages: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -3053,6 +3720,14 @@ packages: resolution: {integrity: sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==} engines: {node: '>=20'} + typed-inject@5.0.0: + resolution: {integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==} + engines: {node: '>=18'} + + typed-rest-client@2.3.1: + resolution: {integrity: sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==} + engines: {node: '>= 16.0.0'} + typescript-eslint@8.71.0: resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3060,6 +3735,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -3074,6 +3754,9 @@ packages: resolution: {integrity: sha512-PnKvRX+Fbxl/usVlTr/31tXIOVAH+8OdC9qaxUQcvurwEnoc3Vk/ipi2Ce8IjMxzK9byfSGdsUBjqqJGc1o5lw==} engines: {node: '>=14'} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} @@ -3127,6 +3810,12 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3140,6 +3829,14 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + vite@8.3.2: resolution: {integrity: sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3183,23 +3880,23 @@ packages: yaml: optional: true - vitest@5.0.3: - resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} - engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.3 - '@vitest/browser-preview': 5.0.3 - '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.3 - '@vitest/coverage-v8': 5.0.3 - '@vitest/ui': 5.0.3 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' - vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -3228,6 +3925,9 @@ packages: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} + weapon-regex@2.0.5: + resolution: {integrity: sha512-BJZkSdtcae9Wb8+hKNtAqR+Q61EMOAuQnpCCHKwT50K7fIWg7/2/RnbKR27YB+sMcYxYLUVj+Asm5Y2XhD8mZg==} + web-worker@1.5.0: resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} @@ -3241,9 +3941,9 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@3.2.1: - resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} - engines: {node: '>=20.11'} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} hasBin: true word-wrap@1.2.5: @@ -3326,30 +4026,275 @@ snapshots: '@actions/io@3.0.2': {} + '@andrewbranch/untar.js@1.0.4': {} + + '@arethetypeswrong/cli@0.18.5': + dependencies: + '@arethetypeswrong/core': 0.18.5 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.8.5 + + '@arethetypeswrong/core@0.18.5': + dependencies: + '@andrewbranch/untar.js': 1.0.4 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.3 + semver: 7.8.5 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/helper-string-parser@7.29.7': - optional: true + '@babel/code-frame@8.0.6': + dependencies: + '@babel/helper-validator-identifier': 8.0.6 + js-tokens: 10.0.0 + + '@babel/compat-data@8.0.5': {} + + '@babel/core@8.0.6': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-compilation-targets': 8.0.6 + '@babel/helpers': 8.0.5 + '@babel/parser': 8.0.6 + '@babel/template': 8.0.0 + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + '@types/gensync': 1.0.5 + convert-source-map: 2.0.0 + empathic: 2.1.0 + gensync: 1.0.0-beta.2 + import-meta-resolve: 4.2.0 + json5: 2.2.3 + obug: 2.2.1 + verkit: 0.3.2 + + '@babel/generator@8.0.6': + dependencies: + '@babel/parser': 8.0.6 + '@babel/types': 8.0.6 + '@jridgewell/gen-mapping': 0.4.0-beta.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@8.0.0': + dependencies: + '@babel/types': 8.0.6 + + '@babel/helper-compilation-targets@8.0.6': + dependencies: + '@babel/compat-data': 8.0.5 + '@babel/helper-validator-option': 8.0.0 + browserslist: 4.29.3 + flru: 1.0.2 + verkit: 0.3.2 + + '@babel/helper-create-class-features-plugin@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-member-expression-to-functions': 8.0.5 + '@babel/helper-optimise-call-expression': 8.0.0 + '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.6) + '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/traverse': 8.0.6 + verkit: 0.3.2 + + '@babel/helper-globals@8.0.6': {} + + '@babel/helper-member-expression-to-functions@8.0.5': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-module-imports@8.0.0': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-module-transforms@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-module-imports': 8.0.0 + '@babel/helper-validator-identifier': 8.0.6 + '@babel/traverse': 8.0.6 + + '@babel/helper-optimise-call-expression@8.0.0': + dependencies: + '@babel/types': 8.0.6 + + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + + '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-member-expression-to-functions': 8.0.5 + '@babel/helper-optimise-call-expression': 8.0.0 + '@babel/traverse': 8.0.6 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-string-parser@8.0.6': {} '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@8.0.6': {} + + '@babel/helper-validator-option@8.0.0': {} + + '@babel/helpers@8.0.5': + dependencies: + '@babel/template': 8.0.0 + '@babel/types': 8.0.6 + '@babel/parser@7.29.9': dependencies: '@babel/types': 7.29.8 - optional: true + + '@babel/parser@8.0.6': + dependencies: + '@babel/types': 8.0.6 + + '@babel/plugin-proposal-decorators@8.0.2(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-create-class-features-plugin': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-syntax-decorators': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-decorators@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-jsx@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-typescript@8.0.3(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-destructuring@8.0.5(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-explicit-resource-management@8.0.5(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-destructuring': 8.0.5(@babel/core@8.0.6) + + '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-module-transforms': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-display-name@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-jsx-development@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/plugin-transform-react-jsx': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-jsx@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-module-imports': 8.0.0 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-syntax-jsx': 8.0.1(@babel/core@8.0.6) + '@babel/types': 8.0.6 + + '@babel/plugin-transform-react-pure-annotations@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-typescript@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-create-class-features-plugin': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/plugin-syntax-typescript': 8.0.3(@babel/core@8.0.6) + + '@babel/preset-react@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-validator-option': 8.0.0 + '@babel/plugin-transform-react-display-name': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-jsx': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-jsx-development': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-pure-annotations': 8.0.1(@babel/core@8.0.6) + + '@babel/preset-typescript@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-validator-option': 8.0.0 + '@babel/plugin-transform-modules-commonjs': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-typescript': 8.0.6(@babel/core@8.0.6) + + '@babel/template@8.0.0': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/traverse@8.0.6': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-globals': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/template': 8.0.0 + '@babel/types': 8.0.6 + obug: 2.2.1 '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - optional: true - '@bcoe/v8-coverage@1.0.2': - optional: true + '@babel/types@8.0.6': + dependencies: + '@babel/helper-string-parser': 8.0.6 + '@babel/helper-validator-identifier': 8.0.6 + + '@bcoe/v8-coverage@1.0.2': {} + + '@braidai/lang@1.1.2': {} '@cacheable/memory@2.2.0': dependencies: @@ -3609,6 +4554,130 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@2.0.8': {} + + '@inquirer/checkbox@5.2.5(@types/node@26.6.4)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/confirm@6.3.2(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/core@12.0.3(@types/node@26.6.4)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.4) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/editor@5.3.3(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/external-editor': 3.0.5(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/expand@5.1.5(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/external-editor@3.0.5(@types/node@26.6.4)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/figures@2.0.9': {} + + '@inquirer/input@5.1.6(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/number@4.2.3(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/password@5.2.2(@types/node@26.6.4)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/prompts@8.7.2(@types/node@26.6.4)': + dependencies: + '@inquirer/checkbox': 5.2.5(@types/node@26.6.4) + '@inquirer/confirm': 6.3.2(@types/node@26.6.4) + '@inquirer/editor': 5.3.3(@types/node@26.6.4) + '@inquirer/expand': 5.1.5(@types/node@26.6.4) + '@inquirer/input': 5.1.6(@types/node@26.6.4) + '@inquirer/number': 4.2.3(@types/node@26.6.4) + '@inquirer/password': 5.2.2(@types/node@26.6.4) + '@inquirer/rawlist': 5.3.5(@types/node@26.6.4) + '@inquirer/search': 4.3.3(@types/node@26.6.4) + '@inquirer/select': 5.2.5(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/rawlist@5.3.5(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/search@4.3.3(@types/node@26.6.4)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/select@5.2.5(@types/node@26.6.4)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.4) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.4) + optionalDependencies: + '@types/node': 26.6.4 + + '@inquirer/type@4.1.1(@types/node@26.6.4)': + optionalDependencies: + '@types/node': 26.6.4 + + '@jridgewell/gen-mapping@0.4.0-beta.0': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.6.0': {} @@ -3626,6 +4695,10 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@loaderkit/resolve@1.0.6': + dependencies: + '@braidai/lang': 1.1.2 + '@microsoft/tsdoc-config@0.18.3': dependencies: '@microsoft/tsdoc': 0.17.1 @@ -3848,6 +4921,10 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 + '@publint/pack@0.1.7': + dependencies: + tinyexec: 1.3.1 + '@rolldown/binding-android-arm-eabi@1.2.12': optional: true @@ -4005,6 +5082,82 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} + + '@stryker-mutator/api@10.0.0': + dependencies: + mutation-testing-metrics: 3.8.4 + mutation-testing-report-schema: 3.8.4 + tslib: 2.8.1 + typed-inject: 5.0.0 + + '@stryker-mutator/core@10.0.0(@types/node@26.6.4)': + dependencies: + '@inquirer/prompts': 8.7.2(@types/node@26.6.4) + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/instrumenter': 10.0.0 + '@stryker-mutator/util': 10.0.0 + ajv: 8.20.0 + chalk: 5.6.2 + commander: 14.0.3 + diff-match-patch: 1.0.5 + emoji-regex: 10.6.0 + execa: 9.6.1 + json-rpc-2.0: 1.8.1 + lodash.groupby: 4.6.0 + minimatch: 10.2.6 + mutation-server-protocol: 0.4.1 + mutation-testing-elements: 3.8.4 + mutation-testing-metrics: 3.8.4 + mutation-testing-report-schema: 3.8.4 + npm-run-path: 6.0.0 + progress: 2.0.3 + rxjs: 7.8.2 + semver: 7.8.5 + source-map: 0.7.6 + tree-kill: 1.2.2 + tslib: 2.8.1 + typed-inject: 5.0.0 + typed-rest-client: 2.3.1 + transitivePeerDependencies: + - '@types/node' + + '@stryker-mutator/instrumenter@10.0.0': + dependencies: + '@babel/core': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.6) + '@babel/plugin-transform-explicit-resource-management': 8.0.5(@babel/core@8.0.6) + '@babel/preset-react': 8.0.1(@babel/core@8.0.6) + '@babel/preset-typescript': 8.0.1(@babel/core@8.0.6) + '@babel/traverse': 8.0.6 + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/util': 10.0.0 + angular-html-parser: 10.11.0 + semver: 7.8.5 + tslib: 2.8.1 + weapon-regex: 2.0.5 + + '@stryker-mutator/typescript-checker@10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(typescript@6.0.3)': + dependencies: + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/core': 10.0.0(@types/node@26.6.4) + '@stryker-mutator/util': 10.0.0 + semver: 7.8.5 + typescript: 6.0.3 + + '@stryker-mutator/util@10.0.0': {} + + '@stryker-mutator/vitest-runner@10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(vitest@4.1.11)': + dependencies: + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/core': 10.0.0(@types/node@26.6.4) + '@stryker-mutator/util': 10.0.0 + semver: 7.8.5 + tslib: 2.8.1 + vitest: 4.1.11(@types/node@26.6.4)(@vitest/coverage-v8@4.1.11)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@stylistic/eslint-plugin@6.0.0-beta.6(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0)) @@ -4053,10 +5206,14 @@ snapshots: '@types/estree@1.0.9': {} + '@types/gensync@1.0.5': {} + '@types/hast@3.0.5': dependencies: '@types/unist': 3.0.3 + '@types/jsesc@2.5.1': {} + '@types/json-schema@7.0.15': {} '@types/katex@0.16.8': {} @@ -4268,37 +5425,60 @@ snapshots: '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': + '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/istanbul-lib-report': 1.0.2 + '@vitest/utils': 4.1.11 ast-v8-to-istanbul: 1.0.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 magicast: 0.5.5 obug: 2.2.1 std-env: 4.3.0 tinyrainbow: 3.2.0 - vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) - optional: true - - '@vitest/istanbul-lib-coverage@1.0.2': - optional: true + vitest: 4.1.11(@types/node@26.6.4)(@vitest/coverage-v8@4.1.11)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) - '@vitest/istanbul-lib-report@1.0.2': + '@vitest/expect@4.1.11': dependencies: - '@vitest/istanbul-lib-coverage': 1.0.2 - optional: true + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.3.0 + tinyrainbow: 3.2.0 - '@vitest/mocker@5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': + '@vitest/mocker@4.1.11(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.3 + '@vitest/spy': 4.1.11 estree-walker: 3.0.3 - magic-string: 1.4.2 + magic-string: 0.30.21 optionalDependencies: vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) - '@vitest/spy@5.0.3': {} + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.2.0 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -4334,6 +5514,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + angular-html-parser@10.11.0: {} + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -4371,10 +5553,11 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 10.0.0 - optional: true balanced-match@4.0.4: {} + baseline-browser-mapping@2.11.27: {} + before-after-hook@4.0.0: {} bottleneck@2.19.5: {} @@ -4387,6 +5570,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.29.3: + dependencies: + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.444 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.3) + cacheable@2.5.0: dependencies: '@cacheable/memory': 2.2.0 @@ -4395,8 +5586,20 @@ snapshots: keyv: 5.6.0 qified: 0.10.1 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} + caniuse-lite@1.0.30001814: {} + ccount@2.0.1: {} chai@6.3.0: {} @@ -4418,6 +5621,10 @@ snapshots: character-entities@2.0.2: {} + chardet@2.2.0: {} + + cjs-module-lexer@1.4.3: {} + clean-stack@5.3.0: dependencies: escape-string-regexp: 5.0.0 @@ -4437,6 +5644,8 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 + cli-width@4.1.0: {} + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -4461,6 +5670,10 @@ snapshots: color-name@1.1.4: {} + commander@10.0.1: {} + + commander@14.0.3: {} + commander@8.3.0: {} comment-parser@1.4.8: {} @@ -4515,6 +5728,8 @@ snapshots: convert-hrtime@5.0.0: {} + convert-source-map@2.0.0: {} + core-util-is@1.0.3: {} cosmiconfig-typescript-loader@6.3.0(@types/node@26.6.4)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): @@ -4559,12 +5774,19 @@ snapshots: dequal@2.0.3: {} + des.js@1.1.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + detect-libc@2.1.2: {} devlop@1.1.0: dependencies: dequal: 2.0.3 + diff-match-patch@1.0.5: {} + diff-sequences@29.6.3: {} dir-glob@3.0.1: @@ -4575,10 +5797,18 @@ snapshots: dependencies: is-obj: 2.0.0 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 + electron-to-chromium@1.5.444: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -4600,10 +5830,16 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-module-lexer@2.3.2: {} + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-toolkit@1.52.0: {} escalade@3.2.0: {} @@ -4805,8 +6041,18 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.8: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fault@2.0.1: dependencies: format: 0.2.2 @@ -4819,6 +6065,8 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + fflate@0.8.3: {} + figures@2.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -4859,6 +6107,8 @@ snapshots: flatted@3.4.4: {} + flru@1.0.2: {} + format@0.2.2: {} formatly@0.7.1: @@ -4879,10 +6129,30 @@ snapshots: function-timeout@1.0.2: {} + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-east-asian-width@1.7.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -4917,6 +6187,8 @@ snapshots: globals@17.13.0: {} + gopd@1.2.0: {} + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -4934,6 +6206,8 @@ snapshots: has-flag@4.0.0: {} + has-symbols@1.1.0: {} + hashery@1.5.1: dependencies: hookified: 1.15.1 @@ -4960,6 +6234,8 @@ snapshots: html-entities@2.6.0: {} + html-escaper@2.0.2: {} + http-proxy-agent@9.1.0(supports-color@7.2.0): dependencies: agent-base: 9.0.0 @@ -4984,6 +6260,10 @@ snapshots: husky@9.1.7: {} + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.12: {} @@ -5052,6 +6332,19 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + java-properties@1.0.2: {} jiti@2.6.1: {} @@ -5060,8 +6353,9 @@ snapshots: jju@1.4.0: {} - js-tokens@10.0.0: - optional: true + js-md4@0.3.2: {} + + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -5074,10 +6368,14 @@ snapshots: '@types/estree': 1.0.9 '@types/node': 26.6.4 + jsesc@3.1.0: {} + json-parse-better-errors@1.0.2: {} json-parse-even-better-errors@2.3.1: {} + json-rpc-2.0@1.8.1: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -5086,6 +6384,8 @@ snapshots: json-with-bigint@3.5.12: {} + json5@2.2.3: {} + jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -5202,6 +6502,8 @@ snapshots: lodash.escaperegexp@4.1.2: {} + lodash.groupby@4.6.0: {} + lodash.isplainobject@4.0.6: {} lodash.isstring@4.0.1: {} @@ -5214,7 +6516,7 @@ snapshots: lru-cache@11.5.3: {} - magic-string@1.4.2: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -5223,7 +6525,6 @@ snapshots: '@babel/parser': 7.29.9 '@babel/types': 7.29.8 source-map-js: 1.2.2 - optional: true make-asynchronous@1.1.0: dependencies: @@ -5231,6 +6532,10 @@ snapshots: type-fest: 4.41.0 web-worker: 1.5.0 + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + markdown-table@3.0.4: {} marked-terminal@7.3.0(marked@15.0.12): @@ -5244,8 +6549,23 @@ snapshots: node-emoji: 2.2.0 supports-hyperlinks: 3.2.0 + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.4.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + marked@15.0.12: {} + marked@9.1.6: {} + + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -5600,6 +6920,8 @@ snapshots: mimic-fn@4.0.0: {} + minimalistic-assert@1.0.1: {} + minimatch@10.2.6: dependencies: brace-expansion: 5.0.12 @@ -5608,8 +6930,24 @@ snapshots: module-replacements@2.11.0: {} + mri@1.2.0: {} + ms@2.1.3: {} + mutation-server-protocol@0.4.1: + dependencies: + zod: 4.6.5 + + mutation-testing-elements@3.8.4: {} + + mutation-testing-metrics@3.8.4: + dependencies: + mutation-testing-report-schema: 3.8.4 + + mutation-testing-report-schema@3.8.4: {} + + mute-stream@3.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -5631,6 +6969,8 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 + node-releases@2.0.57: {} + normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 @@ -5660,6 +7000,8 @@ snapshots: object-deep-merge@2.0.1: {} + object-inspect@1.13.4: {} + obug@2.2.1: {} onetime@6.0.0: @@ -5807,6 +7149,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -5840,16 +7184,29 @@ snapshots: process-nextick-args@2.0.1: {} + progress@2.0.3: {} + proto-list@1.2.4: {} proxy-agent-negotiate@1.1.0: {} + publint@0.3.25: + dependencies: + '@publint/pack': 0.1.7 + package-manager-detector: 1.8.0 + picocolors: 1.1.1 + sade: 1.8.1 + punycode@2.3.1: {} qified@0.10.1: dependencies: hookified: 2.2.0 + qs@6.15.1: + dependencies: + side-channel: 1.1.1 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -5939,8 +7296,18 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.12 '@rolldown/binding-win32-x64-msvc': 1.2.12 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.1.2: {} + safer-buffer@2.1.2: {} + semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) @@ -5986,6 +7353,36 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + signal-exit@4.1.0: {} signale@1.4.0: @@ -6004,6 +7401,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + spawn-error-forwarder@1.0.0: {} spdx-correct@3.2.0: @@ -6029,6 +7428,8 @@ snapshots: dependencies: through2: 2.0.5 + stackback@0.0.2: {} + std-env@4.3.0: {} stream-combiner2@1.1.1: @@ -6165,7 +7566,7 @@ snapshots: dependencies: convert-hrtime: 5.0.0 - tinybench@6.2.0: {} + tinybench@2.9.0: {} tinyexec@1.3.1: {} @@ -6174,8 +7575,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinyrainbow@3.2.0: - optional: true + tinyrainbow@3.2.0: {} to-regex-range@5.0.1: dependencies: @@ -6188,12 +7588,13 @@ snapshots: traverse@0.6.8: {} + tree-kill@1.2.2: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 - tslib@2.8.1: - optional: true + tslib@2.8.1: {} tunnel@0.0.6: {} @@ -6220,6 +7621,16 @@ snapshots: dependencies: tagged-tag: 1.0.0 + typed-inject@5.0.0: {} + + typed-rest-client@2.3.1: + dependencies: + des.js: 1.1.0 + js-md4: 0.3.2 + qs: 6.15.1 + tunnel: 0.0.6 + underscore: 1.13.8 + typescript-eslint@8.71.0(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) @@ -6231,6 +7642,8 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.6.1-rc: {} + typescript@6.0.3: {} uglify-js@3.19.3: @@ -6238,6 +7651,8 @@ snapshots: unbash@4.0.12: {} + underscore@1.13.8: {} + undici-types@8.9.0: {} undici@6.29.0: {} @@ -6284,6 +7699,12 @@ snapshots: universalify@2.0.1: {} + update-browserslist-db@1.3.3(browserslist@4.29.3): + dependencies: + browserslist: 4.29.3 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6297,6 +7718,10 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + validate-npm-package-name@5.0.1: {} + + verkit@0.3.2: {} + vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -6310,30 +7735,38 @@ snapshots: jiti: 2.7.0 yaml: 2.9.1 - vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): + vitest@4.1.11(@types/node@26.6.4)(@vitest/coverage-v8@4.1.11)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): dependencies: - '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) - chai: 6.3.0 + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.4.2 + magic-string: 0.30.21 obug: 2.2.1 + pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.3.0 - tinybench: 6.2.0 + tinybench: 2.9.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 + tinyrainbow: 3.2.0 vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) - why-is-node-running: 3.2.1 + why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.6.4 - '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) transitivePeerDependencies: - msw walk-up-path@4.0.0: {} + weapon-regex@2.0.5: {} + web-worker@1.5.0: {} which-command@0.1.0: {} @@ -6342,7 +7775,10 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@3.2.1: {} + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 word-wrap@1.2.5: {} diff --git a/stryker.shared.ts b/stryker.shared.ts new file mode 100644 index 0000000..67267f0 --- /dev/null +++ b/stryker.shared.ts @@ -0,0 +1,43 @@ +import type { PartialStrykerOptions } from "@stryker-mutator/api/core"; + +export interface PackageStrykerOptions { + /** Every package's own `stryker.config.ts` passes this. Left unset (`undefined`) until a real CI run has actually measured a baseline. A mutation-score gate chosen before ever running the suite is exactly the arbitrary magic number this workspace's own conventions exist to avoid. */ + readonly breakThreshold?: number; + /** Extra glob patterns to mutate, appended to the package's own `src/**\/*.ts` default. */ + readonly additionalMutate?: readonly string[]; +} + +/** + * Deliberately no `incremental`/`incrementalFile`: `@stryker-mutator/vitest-runner`'s own `capabilities()` reports no test-location support, so per StrykerJS's own documented incremental-mode limitations ("test runner plugins offer varying levels of support... 'Nothing' means only mutant changes are detected, not test changes"), Stryker has no way to notice a test file changed and will silently reuse a stale cached verdict for every mutant that test used to cover. Confirmed directly: a package's own mutation score stayed at its pre-fix value in CI, reusing a restored incremental report, even after new tests were added specifically to kill previously-surviving mutants in that exact package. + */ +export function packageStrykerConfig( + options: PackageStrykerOptions = {}, +): PartialStrykerOptions { + const { breakThreshold, additionalMutate = [] } = options; + + return { + packageManager: "pnpm", + testRunner: "vitest", + // Explicit, not Stryker's own default auto-discovery: that default resolves `@stryker-mutator/*` plugins relative to wherever `@stryker-mutator/core` itself physically lives, which under pnpm's isolated node_modules is a separate virtual-store entry from the checker/test-runner packages each package installs alongside it. Confirmed directly (`--logLevel debug` showed Stryker scanning core's own node_modules/@stryker-mutator/, which pnpm never populates with sibling plugins), producing "Cannot find Checker plugin \"typescript\"" even though the package was genuinely installed and resolvable via plain Node module resolution the whole time. An explicit list uses ordinary resolution from this config's own location instead, which pnpm's per-package node_modules symlink already satisfies. + plugins: [ + "@stryker-mutator/typescript-checker", + "@stryker-mutator/vitest-runner", + ], + checkers: ["typescript"], + coverageAnalysis: "perTest", + mutate: [ + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/test-support/**", + // A worker file a test spawns through `new Worker(new URL(...))` runs in its own thread, outside the sandboxed vitest process Stryker instruments — so its mutants can never be killed, only error or sit uncovered. Mutating test infrastructure no in-process test can observe is noise, not coverage. + "!src/**/*.worker.ts", + ...additionalMutate, + ], + reporters: ["html", "clear-text", "progress"], + htmlReporter: { fileName: "reports/mutation/mutation.html" }, + thresholds: + breakThreshold === undefined + ? { high: 90, low: 70 } + : { high: 90, low: 70, break: breakThreshold }, + }; +} diff --git a/tsconfig.build.json b/tsconfig.build.json index c5369ea..40bedd5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,8 @@ { "files": [], - "references": [] + "references": [ + { + "path": "packages/core/graph/dag/tsconfig.build.json" + } + ] } diff --git a/tsconfig.json b/tsconfig.json index be1f183..0b5cd16 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "commit-types.ts", "release.config.ts", "eslint.shared.ts", + "stryker.shared.ts", ".github/scripts/**/*.ts" ] } diff --git a/turbo.json b/turbo.json index 2881a5d..0dd8497 100644 --- a/turbo.json +++ b/turbo.json @@ -6,6 +6,7 @@ ".tool-versions", "prettier.config.ts", "tsconfig.base.json", + "stryker.shared.ts", "knip.config.ts", "syncpack.config.ts" ],