@intentius/chant 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/handlers/graph.d.ts +2 -2
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +6 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/graph-dot.d.ts +12 -0
- package/dist/graph-dot.d.ts.map +1 -0
- package/dist/graph-layout.d.ts +39 -0
- package/dist/graph-layout.d.ts.map +1 -0
- package/dist/graph-lens.d.ts +30 -0
- package/dist/graph-lens.d.ts.map +1 -0
- package/dist/graph-mermaid.d.ts +17 -0
- package/dist/graph-mermaid.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/reconcile.d.ts +111 -3
- package/dist/reconcile.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/handlers/graph.test.ts +166 -48
- package/src/cli/handlers/graph.ts +56 -13
- package/src/cli/main.test.ts +16 -0
- package/src/cli/main.ts +11 -2
- package/src/cli/registry.ts +6 -0
- package/src/graph-dot.test.ts +68 -0
- package/src/graph-dot.ts +58 -0
- package/src/graph-layout.ts +104 -0
- package/src/graph-lens.test.ts +80 -0
- package/src/graph-lens.ts +127 -0
- package/src/graph-mermaid.test.ts +61 -0
- package/src/graph-mermaid.ts +85 -0
- package/src/index.ts +4 -0
- package/src/reconcile.test.ts +148 -0
- package/src/reconcile.ts +249 -3
|
@@ -2,8 +2,8 @@ import type { CommandContext } from "../registry.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* `chant graph` — the Op dependency graph by default; `--stacks` renders the
|
|
4
4
|
* cross-stack apply-ordering graph (edges, order, waves) chant computes from
|
|
5
|
-
* cross-lexicon references; `--format ir` emits the
|
|
6
|
-
*
|
|
5
|
+
* cross-lexicon references; `--format ir|mermaid` emits the lint-gated
|
|
6
|
+
* entity-graph IR (or a Mermaid flowchart of it) for diagrams (#493/#496).
|
|
7
7
|
*/
|
|
8
8
|
export declare function runGraph(ctx: CommandContext): Promise<number>;
|
|
9
9
|
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnE"}
|
package/dist/cli/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAe9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAe9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA6IpD"}
|
package/dist/cli/registry.d.ts
CHANGED
|
@@ -58,6 +58,12 @@ export interface ParsedArgs {
|
|
|
58
58
|
stacks?: boolean;
|
|
59
59
|
/** `chant graph --format ir --detail <0..3>` — graph IR detail tier */
|
|
60
60
|
detail?: number;
|
|
61
|
+
/** `chant graph --lens <kind>:<target>` — focus the graph IR on a slice */
|
|
62
|
+
lens?: string;
|
|
63
|
+
/** `chant graph --lens blast:<node> --up` — include upstream producers */
|
|
64
|
+
up?: boolean;
|
|
65
|
+
/** `chant graph --lens blast:<node> --down` — include downstream dependents */
|
|
66
|
+
down?: boolean;
|
|
61
67
|
/** `chant lifecycle affected --base <ref>` — base git ref to diff against */
|
|
62
68
|
base?: string;
|
|
63
69
|
/** `chant lifecycle affected --head <ref>` — head git ref (default: working tree) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GraphIR } from "./graph-ir.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render the graph IR as Graphviz DOT. Pure text — no `dot` needed to produce
|
|
4
|
+
* it. Two consumers: render directly with `dot -Tsvg` (mingrammer-style), or
|
|
5
|
+
* feed it to a layout engine for node positions that a custom painter draws
|
|
6
|
+
* (the rackattack pattern; see {@link toLayout}). See issue #497 / epic #492.
|
|
7
|
+
*
|
|
8
|
+
* Consumes whatever IR it is given, so `--detail` (and future `--lens`) flow
|
|
9
|
+
* through for free.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toDot(ir: GraphIR): string;
|
|
12
|
+
//# sourceMappingURL=graph-dot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-dot.d.ts","sourceRoot":"","sources":["../src/graph-dot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CA2BzC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout-position export — node coordinates a custom painter consumes (the
|
|
3
|
+
* rackattack pattern: Graphviz lays out, the painter draws). Graphviz is used
|
|
4
|
+
* for LAYOUT ONLY; its rendering is discarded. See issue #497 / epic #492.
|
|
5
|
+
*
|
|
6
|
+
* The {@link LayoutEngine} interface exists so a pure-JS engine (elkjs/dagre)
|
|
7
|
+
* can drop in later for a zero-native-dependency path — closing the install gap
|
|
8
|
+
* so a painter can run without `dot`.
|
|
9
|
+
*/
|
|
10
|
+
/** A laid-out position in the engine's coordinate space. */
|
|
11
|
+
export interface Point {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
15
|
+
/** Node positions plus the overall canvas size a painter needs. */
|
|
16
|
+
export interface Layout {
|
|
17
|
+
/** Canvas width in the engine's coordinate space. */
|
|
18
|
+
width: number;
|
|
19
|
+
/** Canvas height in the engine's coordinate space. */
|
|
20
|
+
height: number;
|
|
21
|
+
/** Each node's centre position, ordered by id for deterministic output. */
|
|
22
|
+
nodes: Array<{
|
|
23
|
+
id: string;
|
|
24
|
+
} & Point>;
|
|
25
|
+
}
|
|
26
|
+
/** Turns DOT into node positions. The painter consumes the result; it never
|
|
27
|
+
* asks the engine to paint. */
|
|
28
|
+
export interface LayoutEngine {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
layout(dot: string): Promise<Layout>;
|
|
31
|
+
}
|
|
32
|
+
/** Layout via `dot -Tjson`. Requires Graphviz (`brew install graphviz`). */
|
|
33
|
+
export declare class GraphvizLayout implements LayoutEngine {
|
|
34
|
+
readonly name = "graphviz";
|
|
35
|
+
layout(dot: string): Promise<Layout>;
|
|
36
|
+
}
|
|
37
|
+
/** Parse `dot -Tjson` output into a {@link Layout}. Pure; exported for testing. */
|
|
38
|
+
export declare function parseDotJson(json: string): Layout;
|
|
39
|
+
//# sourceMappingURL=graph-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-layout.d.ts","sourceRoot":"","sources":["../src/graph-layout.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,mEAAmE;AACnE,MAAM,WAAW,MAAM;IACrB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC,CAAC;CACtC;AAED;+BAC+B;AAC/B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,4EAA4E;AAC5E,qBAAa,cAAe,YAAW,YAAY;IACjD,QAAQ,CAAC,IAAI,cAAc;IAErB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAG3C;AAsCD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBjD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { GraphIR } from "./graph-ir.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lenses — focus the graph IR on a slice without touching the source. Pure
|
|
4
|
+
* IR → IR filters, composable with detail tiers (apply a lens, then view the
|
|
5
|
+
* result at any `--detail`). See issue #495 / epic #492.
|
|
6
|
+
*
|
|
7
|
+
* - `lexicon:<name>` — only nodes in a lexicon
|
|
8
|
+
* - `stack:<name>` — only one stack's nodes (stacks map to lexicon partitions today)
|
|
9
|
+
* - `blast:<nodeId>` — the transitive neighbourhood of a node: what it depends
|
|
10
|
+
* on (`--up`), what depends on it (`--down`), or both (default)
|
|
11
|
+
*
|
|
12
|
+
* Every lens drops edges that would dangle and rebuilds group metadata from the
|
|
13
|
+
* surviving nodes, so the result is always a self-consistent graph.
|
|
14
|
+
*/
|
|
15
|
+
export interface LensSpec {
|
|
16
|
+
kind: "lexicon" | "stack" | "blast";
|
|
17
|
+
target: string;
|
|
18
|
+
/** blast: include upstream producers (what the node depends on). */
|
|
19
|
+
up: boolean;
|
|
20
|
+
/** blast: include downstream dependents (what depends on the node). */
|
|
21
|
+
down: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Parse a `--lens` spec. Throws a descriptive Error on a malformed spec. */
|
|
24
|
+
export declare function parseLens(spec: string, opts?: {
|
|
25
|
+
up?: boolean;
|
|
26
|
+
down?: boolean;
|
|
27
|
+
}): LensSpec;
|
|
28
|
+
/** Apply a lens to the IR. Throws a descriptive Error when the target matches nothing. */
|
|
29
|
+
export declare function applyLens(ir: GraphIR, lens: LensSpec): GraphIR;
|
|
30
|
+
//# sourceMappingURL=graph-lens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-lens.d.ts","sourceRoot":"","sources":["../src/graph-lens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,EAAE,EAAE,OAAO,CAAC;IACZ,uEAAuE;IACvE,IAAI,EAAE,OAAO,CAAC;CACf;AAID,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,QAAQ,CAkB7F;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAI9D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GraphIR } from "./graph-ir.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render the graph IR as a Mermaid `flowchart`. Mermaid is the zero-install
|
|
4
|
+
* default — it renders in GitHub, docs, and browsers with no native dependency,
|
|
5
|
+
* so `chant graph --format mermaid` gives a diagram out of the box without the
|
|
6
|
+
* standalone painter. Lower fidelity than a custom painter, but portable.
|
|
7
|
+
*
|
|
8
|
+
* Consumes whatever IR it is given, so it honours `--detail` and `--lens` for
|
|
9
|
+
* free (those are IR → IR transforms). See issue #496 / epic #492.
|
|
10
|
+
*
|
|
11
|
+
* Known limits: Mermaid owns layout, so there is little control over node
|
|
12
|
+
* placement, and very large graphs get hard to read — that is the trade-off for
|
|
13
|
+
* zero-install portability. Reach for the graphviz/custom-painter path (#497,
|
|
14
|
+
* pinhole) when fidelity matters.
|
|
15
|
+
*/
|
|
16
|
+
export declare function toMermaid(ir: GraphIR): string;
|
|
17
|
+
//# sourceMappingURL=graph-mermaid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-mermaid.d.ts","sourceRoot":"","sources":["../src/graph-mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAgC7C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export * from "./discovery/cache.js";
|
|
|
26
26
|
export * from "./build.js";
|
|
27
27
|
export * from "./graph-ir.js";
|
|
28
28
|
export * from "./graph-detail.js";
|
|
29
|
+
export * from "./graph-mermaid.js";
|
|
30
|
+
export * from "./graph-dot.js";
|
|
31
|
+
export * from "./graph-layout.js";
|
|
32
|
+
export * from "./graph-lens.js";
|
|
29
33
|
export * from "./detectLexicon.js";
|
|
30
34
|
export * from "./lint/parser.js";
|
|
31
35
|
export * from "./lint/rule.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAClE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5F,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAClE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5F,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/reconcile.d.ts
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* cap + a pluggable check runner).
|
|
9
9
|
*
|
|
10
10
|
* A "warden" (e.g. github-warden) builds its provider-specific resource diffing,
|
|
11
|
-
* live-state types, and domain guardrails on top of this
|
|
11
|
+
* live-state types, and domain guardrails on top of this, and drives them with
|
|
12
|
+
* the generic `runReconcile` loop + `Cycle` interface (below). It complements
|
|
12
13
|
* chant's `ownership.ts` marker contract: ownership markers make a `delete`
|
|
13
14
|
* precise; this module decides *which* entries are creates / updates / deletes
|
|
14
15
|
* in the first place.
|
|
15
16
|
*
|
|
16
|
-
* Consumed as `@intentius/chant/reconcile`.
|
|
17
|
-
*
|
|
17
|
+
* Consumed as `@intentius/chant/reconcile`. The diff and guardrail primitives
|
|
18
|
+
* are pure and clock-free; `runReconcile` is the orchestration loop and is the
|
|
19
|
+
* only part that drives I/O (through the provider's `Cycle` implementations).
|
|
18
20
|
*/
|
|
19
21
|
/** A single field-level change: what the old value was and what it will become. */
|
|
20
22
|
export interface FieldChange {
|
|
@@ -144,4 +146,110 @@ export declare function removalDeltaCap(changeSet: ChangeSet, opts?: RemovalDelt
|
|
|
144
146
|
* caller composes provider-specific checks (e.g. an admin floor) as closures.
|
|
145
147
|
*/
|
|
146
148
|
export declare function runGuardrailChecks(changeSet: ChangeSet, checks: GuardrailCheck[]): GuardrailResult;
|
|
149
|
+
/** Controls how a cycle tracks its API usage against a shared request budget. */
|
|
150
|
+
export interface RateBudget {
|
|
151
|
+
/** Remaining request capacity for this run. */
|
|
152
|
+
readonly remaining: number;
|
|
153
|
+
/** True once `remaining` has reached zero. */
|
|
154
|
+
readonly exhausted: boolean;
|
|
155
|
+
/** Decrement by `n` (default 1). Throws `BudgetExhaustedError` if exhausted. */
|
|
156
|
+
use(n?: number): void;
|
|
157
|
+
}
|
|
158
|
+
/** Thrown when a cycle or apply step attempts to use an exhausted budget. */
|
|
159
|
+
export declare class BudgetExhaustedError extends Error {
|
|
160
|
+
constructor(message?: string);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A reconcile cycle: fetch live state for one resource domain, build desired
|
|
164
|
+
* state from config, and apply a single `ChangeSetEntry` back to the provider.
|
|
165
|
+
* Generic over the provider client (`TClient`), the per-scope config slice
|
|
166
|
+
* (`TConfig`), the live snapshot (`TLive`), and caller-supplied scope (`TScope`).
|
|
167
|
+
*
|
|
168
|
+
* `scopeId` is the current scope being iterated (e.g. an org login or group
|
|
169
|
+
* path); cycles use it — not `TScope` — for provider API paths, so a multi-scope
|
|
170
|
+
* config targets the right scope. Every network call must charge `budget`.
|
|
171
|
+
*/
|
|
172
|
+
export interface Cycle<TClient, TConfig, TLive, TScope = unknown> {
|
|
173
|
+
/** Human-readable name, e.g. "branch-protection". */
|
|
174
|
+
name: string;
|
|
175
|
+
fetchLive(client: TClient, scopeId: string, scope: TScope, budget: RateBudget): Promise<TLive>;
|
|
176
|
+
buildDesired(config: TConfig, scopeId: string, scope: TScope): TConfig;
|
|
177
|
+
apply(client: TClient, entry: ChangeSetEntry, scopeId: string, scope: TScope, budget: RateBudget): Promise<void>;
|
|
178
|
+
}
|
|
179
|
+
/** Per-cycle outcome recorded in the run result. */
|
|
180
|
+
export interface CycleResult {
|
|
181
|
+
name: string;
|
|
182
|
+
/** Scope id this result is for (e.g. an org login). */
|
|
183
|
+
org: string;
|
|
184
|
+
counts: {
|
|
185
|
+
create: number;
|
|
186
|
+
update: number;
|
|
187
|
+
delete: number;
|
|
188
|
+
};
|
|
189
|
+
guardrails: GuardrailResult;
|
|
190
|
+
applied: ChangeSetEntry[];
|
|
191
|
+
failed: Array<{
|
|
192
|
+
entry: ChangeSetEntry;
|
|
193
|
+
error: string;
|
|
194
|
+
}>;
|
|
195
|
+
plan: string;
|
|
196
|
+
guardrailBlocked: boolean;
|
|
197
|
+
}
|
|
198
|
+
/** A cycle that errored during `fetchLive`/`buildDesired` (non-budget error). */
|
|
199
|
+
export interface CycleError {
|
|
200
|
+
name: string;
|
|
201
|
+
org: string;
|
|
202
|
+
stage: "fetchLive" | "buildDesired";
|
|
203
|
+
error: string;
|
|
204
|
+
}
|
|
205
|
+
/** Work that could not complete due to budget exhaustion. */
|
|
206
|
+
export interface DeferredWork {
|
|
207
|
+
skippedCycles: string[];
|
|
208
|
+
skippedEntries: Array<{
|
|
209
|
+
cycleName: string;
|
|
210
|
+
entry: ChangeSetEntry;
|
|
211
|
+
}>;
|
|
212
|
+
}
|
|
213
|
+
/** Structured result from a single `runReconcile` call. */
|
|
214
|
+
export interface ReconcileResult {
|
|
215
|
+
mode: "dry-run" | "apply";
|
|
216
|
+
completed: boolean;
|
|
217
|
+
cycles: CycleResult[];
|
|
218
|
+
errored: CycleError[];
|
|
219
|
+
deferred: DeferredWork;
|
|
220
|
+
budgetRemaining: number;
|
|
221
|
+
}
|
|
222
|
+
/** Options for `runReconcile`. */
|
|
223
|
+
export interface RunReconcileOptions<TClient, TConfig, TLive, TScope = unknown> {
|
|
224
|
+
/** Per-scope configs to reconcile, keyed by scope id (e.g. org login). */
|
|
225
|
+
scopes: Record<string, TConfig>;
|
|
226
|
+
/** Authed provider client, passed to every cycle. */
|
|
227
|
+
client: TClient;
|
|
228
|
+
/** Cycles to run; each runs against every scope in `scopes`. */
|
|
229
|
+
cycles: Array<Cycle<TClient, TConfig, TLive, TScope>>;
|
|
230
|
+
/** Scope forwarded to each cycle (filter/cursor); does not vary by scopeId. */
|
|
231
|
+
scope?: TScope;
|
|
232
|
+
/** "dry-run" (default) computes + reports; "apply" mutates after guardrails. */
|
|
233
|
+
mode?: "dry-run" | "apply";
|
|
234
|
+
/** Provider diff: turn (desired, live) into a ChangeSet for one scope. */
|
|
235
|
+
diff: (scopeId: string, desired: TConfig, live: TLive, opts: DiffOptions) => ChangeSet;
|
|
236
|
+
/** Guardrail check over the change set + live. Defaults to always-ok. */
|
|
237
|
+
guardrails?: (changeSet: ChangeSet, live: TLive) => GuardrailResult;
|
|
238
|
+
/** Diff options forwarded to `diff`. */
|
|
239
|
+
diffOptions?: DiffOptions;
|
|
240
|
+
/** Apply even when guardrails trip. Default false. */
|
|
241
|
+
allowGuardrailOverride?: boolean;
|
|
242
|
+
/** Max requests for the run (across all cycles). Default 1000. */
|
|
243
|
+
requestBudget?: number;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Run the reconcile loop. For each scope in `scopes` and each cycle:
|
|
247
|
+
* 1. fetchLive 2. buildDesired 3. diff 4. guardrails
|
|
248
|
+
* 5a. dry-run: record the plan 5b. apply: apply each entry (if guardrails pass)
|
|
249
|
+
*
|
|
250
|
+
* Budget-aware (stops cleanly + records deferred work on exhaustion) and
|
|
251
|
+
* fault-tolerant (a cycle that errors is recorded and the run continues).
|
|
252
|
+
* Returns a structured `ReconcileResult`.
|
|
253
|
+
*/
|
|
254
|
+
export declare function runReconcile<TClient, TConfig, TLive, TScope = unknown>(opts: RunReconcileOptions<TClient, TConfig, TLive, TScope>): Promise<ReconcileResult>;
|
|
147
255
|
//# sourceMappingURL=reconcile.d.ts.map
|
package/dist/reconcile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import type { ParsedArgs } from "../registry";
|
|
3
|
+
import { DECLARABLE_MARKER, type Declarable } from "../../declarable";
|
|
4
|
+
import { AttrRef } from "../../attrref";
|
|
3
5
|
|
|
4
6
|
const discoverOpsMock = vi.fn();
|
|
5
7
|
vi.mock("../../op/discover", () => ({
|
|
6
8
|
discoverOps: () => discoverOpsMock(),
|
|
7
9
|
}));
|
|
8
10
|
|
|
11
|
+
const discoverMock = vi.fn();
|
|
12
|
+
vi.mock("../../discovery/index", () => ({
|
|
13
|
+
discover: () => discoverMock(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const lintMock = vi.fn();
|
|
17
|
+
vi.mock("../commands/lint", () => ({
|
|
18
|
+
lintCommand: () => lintMock(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Avoid shelling out to graphviz in tests; the format dispatch is what matters.
|
|
22
|
+
const layoutMock = vi.fn();
|
|
23
|
+
vi.mock("../../graph-layout", () => ({
|
|
24
|
+
GraphvizLayout: class {
|
|
25
|
+
layout() {
|
|
26
|
+
return layoutMock();
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
9
31
|
const { runGraph } = await import("./graph");
|
|
10
32
|
|
|
11
33
|
function makeArgs(overrides: Partial<ParsedArgs> = {}): ParsedArgs {
|
|
@@ -20,6 +42,18 @@ function makeOp(name: string, depends: string[] = []): [string, { config: { name
|
|
|
20
42
|
return [name, { config: { name, depends } }];
|
|
21
43
|
}
|
|
22
44
|
|
|
45
|
+
function decl<T extends object>(base: T): Declarable & T {
|
|
46
|
+
return { [DECLARABLE_MARKER]: true, ...base } as Declarable & T;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A small two-lexicon graph: vpc <- subnet (gcp), subnet <- pod (k8s). */
|
|
50
|
+
function sampleEntities(): Map<string, Declarable> {
|
|
51
|
+
const vpc = decl({ lexicon: "gcp", entityType: "Vpc" });
|
|
52
|
+
const subnet = decl({ lexicon: "gcp", entityType: "Subnet", props: { network: new AttrRef(vpc, "id") } });
|
|
53
|
+
const pod = decl({ lexicon: "k8s", entityType: "Pod", props: { net: new AttrRef(subnet, "id") } });
|
|
54
|
+
return new Map<string, Declarable>([["vpc", vpc], ["subnet", subnet], ["pod", pod]]);
|
|
55
|
+
}
|
|
56
|
+
|
|
23
57
|
describe("runGraph", () => {
|
|
24
58
|
let stdoutBuf: string[];
|
|
25
59
|
let stderrBuf: string[];
|
|
@@ -30,62 +64,146 @@ describe("runGraph", () => {
|
|
|
30
64
|
vi.spyOn(console, "log").mockImplementation((s: string) => { stdoutBuf.push(s); });
|
|
31
65
|
vi.spyOn(console, "error").mockImplementation((s: string) => { stderrBuf.push(s); });
|
|
32
66
|
discoverOpsMock.mockReset();
|
|
67
|
+
discoverMock.mockReset();
|
|
68
|
+
lintMock.mockReset();
|
|
69
|
+
layoutMock.mockReset();
|
|
33
70
|
});
|
|
34
71
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
describe("Op graph (default)", () => {
|
|
73
|
+
test("prints 'No Ops found' when discovery is empty", async () => {
|
|
74
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map(), errors: [] });
|
|
75
|
+
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
76
|
+
expect(exit).toBe(0);
|
|
77
|
+
expect(stdoutBuf.join("\n")).toContain("No Ops found");
|
|
78
|
+
});
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
test("prints 'No Op dependencies' when ops have no depends", async () => {
|
|
81
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([makeOp("solo")]), errors: [] });
|
|
82
|
+
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
83
|
+
expect(exit).toBe(0);
|
|
84
|
+
expect(stdoutBuf.join("\n")).toContain("No Op dependencies");
|
|
46
85
|
});
|
|
47
|
-
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
48
|
-
expect(exit).toBe(0);
|
|
49
|
-
expect(stdoutBuf.join("\n")).toContain("No Op dependencies");
|
|
50
|
-
});
|
|
51
86
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
])
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
expect(exit).toBe(0);
|
|
62
|
-
const out = stdoutBuf.join("\n");
|
|
63
|
-
expect(out).toContain("infra → app");
|
|
64
|
-
});
|
|
87
|
+
test("prints `dep -> name` edge per dependency", async () => {
|
|
88
|
+
discoverOpsMock.mockResolvedValue({
|
|
89
|
+
ops: new Map([makeOp("infra"), makeOp("app", ["infra"])]),
|
|
90
|
+
errors: [],
|
|
91
|
+
});
|
|
92
|
+
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
93
|
+
expect(exit).toBe(0);
|
|
94
|
+
expect(stdoutBuf.join("\n")).toContain("infra → app");
|
|
95
|
+
});
|
|
65
96
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
test("handles multi-edge graphs", async () => {
|
|
98
|
+
discoverOpsMock.mockResolvedValue({
|
|
99
|
+
ops: new Map([makeOp("a"), makeOp("b", ["a"]), makeOp("c", ["a", "b"])]),
|
|
100
|
+
errors: [],
|
|
101
|
+
});
|
|
102
|
+
await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
103
|
+
const out = stdoutBuf.join("\n");
|
|
104
|
+
expect(out).toContain("a → b");
|
|
105
|
+
expect(out).toContain("a → c");
|
|
106
|
+
expect(out).toContain("b → c");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("forwards discovery errors to stderr", async () => {
|
|
110
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map(), errors: ["failed to parse ops/bad.op.ts"] });
|
|
111
|
+
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
112
|
+
expect(exit).toBe(0);
|
|
113
|
+
expect(stderrBuf.join("\n")).toContain("failed to parse ops/bad.op.ts");
|
|
114
|
+
});
|
|
80
115
|
});
|
|
81
116
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
errors: [
|
|
117
|
+
describe("graph IR views (--format ir|mermaid|dot|layout)", () => {
|
|
118
|
+
const lintClean = (): void => { lintMock.mockResolvedValue({ success: true }); };
|
|
119
|
+
const discovered = (): void => {
|
|
120
|
+
discoverMock.mockResolvedValue({ entities: sampleEntities(), errors: [], dependencies: new Map(), sourceFiles: [] });
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
test("--format ir emits the graph IR as JSON", async () => {
|
|
124
|
+
lintClean(); discovered();
|
|
125
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
126
|
+
expect(exit).toBe(0);
|
|
127
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
128
|
+
expect(ir.nodes.map((n: { id: string }) => n.id).sort()).toEqual(["pod", "subnet", "vpc"]);
|
|
129
|
+
expect(ir.edges).toContainEqual({ from: "subnet", to: "vpc", kind: "ref", viaAttr: "network" });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("lint gate: refuses to emit when source has lint errors", async () => {
|
|
133
|
+
lintMock.mockResolvedValue({ success: false });
|
|
134
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
135
|
+
expect(exit).toBe(1);
|
|
136
|
+
expect(stdoutBuf.join("\n")).toBe("");
|
|
137
|
+
expect(stderrBuf.join("\n")).toMatch(/lint errors/i);
|
|
138
|
+
expect(discoverMock).not.toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("--format mermaid emits a flowchart", async () => {
|
|
142
|
+
lintClean(); discovered();
|
|
143
|
+
const exit = await runGraph({ args: makeArgs({ format: "mermaid" }), plugins: [], serializers: [] });
|
|
144
|
+
expect(exit).toBe(0);
|
|
145
|
+
expect(stdoutBuf.join("\n")).toContain("flowchart TD");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("--format dot emits a digraph", async () => {
|
|
149
|
+
lintClean(); discovered();
|
|
150
|
+
const exit = await runGraph({ args: makeArgs({ format: "dot" }), plugins: [], serializers: [] });
|
|
151
|
+
expect(exit).toBe(0);
|
|
152
|
+
expect(stdoutBuf.join("\n")).toContain("digraph chant {");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("--format layout emits positions from the layout engine", async () => {
|
|
156
|
+
lintClean(); discovered();
|
|
157
|
+
layoutMock.mockResolvedValue({ width: 100, height: 50, nodes: [{ id: "vpc", x: 1, y: 2 }] });
|
|
158
|
+
const exit = await runGraph({ args: makeArgs({ format: "layout" }), plugins: [], serializers: [] });
|
|
159
|
+
expect(exit).toBe(0);
|
|
160
|
+
expect(JSON.parse(stdoutBuf.join("\n"))).toMatchObject({ width: 100, nodes: [{ id: "vpc", x: 1, y: 2 }] });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("--format layout reports a clear error when the engine fails (e.g. dot missing)", async () => {
|
|
164
|
+
lintClean(); discovered();
|
|
165
|
+
layoutMock.mockRejectedValue(new Error("could not run 'dot'"));
|
|
166
|
+
const exit = await runGraph({ args: makeArgs({ format: "layout" }), plugins: [], serializers: [] });
|
|
167
|
+
expect(exit).toBe(1);
|
|
168
|
+
expect(stderrBuf.join("\n")).toContain("could not run 'dot'");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("--detail 0 collapses to one node per lexicon", async () => {
|
|
172
|
+
lintClean(); discovered();
|
|
173
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", detail: 0 }), plugins: [], serializers: [] });
|
|
174
|
+
expect(exit).toBe(0);
|
|
175
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
176
|
+
expect(ir.nodes.map((n: { id: string }) => n.id).sort()).toEqual(["gcp", "k8s"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("rejects an out-of-range --detail", async () => {
|
|
180
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", detail: 9 }), plugins: [], serializers: [] });
|
|
181
|
+
expect(exit).toBe(1);
|
|
182
|
+
expect(stderrBuf.join("\n")).toMatch(/detail/i);
|
|
183
|
+
expect(lintMock).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("--lens lexicon:gcp filters to that lexicon", async () => {
|
|
187
|
+
lintClean(); discovered();
|
|
188
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", lens: "lexicon:gcp" }), plugins: [], serializers: [] });
|
|
189
|
+
expect(exit).toBe(0);
|
|
190
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
191
|
+
expect(ir.nodes.map((n: { id: string }) => n.id).sort()).toEqual(["subnet", "vpc"]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("--lens with a bad spec errors out", async () => {
|
|
195
|
+
lintClean(); discovered();
|
|
196
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", lens: "nope" }), plugins: [], serializers: [] });
|
|
197
|
+
expect(exit).toBe(1);
|
|
198
|
+
expect(stderrBuf.join("\n")).toMatch(/lens/i);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("--format ir forwards discovery errors and exits non-zero", async () => {
|
|
202
|
+
lintClean();
|
|
203
|
+
discoverMock.mockResolvedValue({ entities: new Map(), errors: [{ message: "boom" }], dependencies: new Map(), sourceFiles: [] });
|
|
204
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
205
|
+
expect(exit).toBe(1);
|
|
206
|
+
expect(stderrBuf.join("\n")).toContain("boom");
|
|
86
207
|
});
|
|
87
|
-
const exit = await runGraph({ args: makeArgs(), plugins: [], serializers: [] });
|
|
88
|
-
expect(exit).toBe(0);
|
|
89
|
-
expect(stderrBuf.join("\n")).toContain("failed to parse ops/bad.op.ts");
|
|
90
208
|
});
|
|
91
209
|
});
|