@archwall/core 0.1.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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/index.cjs +1400 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +721 -0
- package/dist/index.d.mts +721 -0
- package/dist/index.mjs +1349 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal.cjs +12 -0
- package/dist/internal.d.cts +106 -0
- package/dist/internal.d.mts +106 -0
- package/dist/internal.mjs +2 -0
- package/dist/prepare-BJHgDEui.mjs +748 -0
- package/dist/prepare-BJHgDEui.mjs.map +1 -0
- package/dist/prepare-C1FfL8Qd.cjs +921 -0
- package/dist/prepare-C1FfL8Qd.cjs.map +1 -0
- package/dist/transform-CnUPOO0E.d.cts +638 -0
- package/dist/transform-CnUPOO0E.d.mts +638 -0
- package/package.json +54 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
import { $ as THIRD_PARTY_KINDS, A as primarySourceLocation, B as GraphDelivery, C as ViolationLocation, D as locationsOf, E as fingerprintOf, F as defineClassifier, G as MODULE_ID_SCHEMES, H as GraphMutation, I as Capability, J as ModuleKind, K as ModuleId, L as Edge, M as Classifier, N as ClassifierContext, O as primaryEdge, P as TagPatch, Q as SourceLocation, R as EdgeKind, S as ViolationInput, T as countBySeverity, U as HostInfo, W as IR_VERSION, X as ProjectGraph, Y as ModuleNode, Z as ProjectGraphInit, _ as WellKnownDiagnosticCode, a as defineGraphComputation, at as isFirstParty, b as SeverityCounts, c as GraphQuery, et as WellKnownCapability, f as Diagnostic, g as RuleSkippedDetails, h as EmptyScopeDetails, i as GraphComputation, it as irMajor, j as renderMessage, k as primaryModule, l as ModuleFilter, m as DiagnosticSeverity, n as TransformContext, nt as assertIrCompatible, o as EdgeFilter, ot as isThirdParty, p as DiagnosticCode, q as ModuleIdScheme, r as defineTransform, rt as displayModuleId, st as parseModuleId, t as GraphTransform, tt as WellKnownEdgeKind, u as ModuleSelection, v as FINGERPRINT_SCHEME, w as compareViolations, x as Violation, y as Severity, z as FIRST_PARTY_KINDS } from "./transform-CnUPOO0E.mjs";
|
|
2
|
+
//#region src/analysis/scc.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Strongly connected components over static+reexport edges (a dynamic import is a
|
|
5
|
+
* legal cycle-breaker). Iterative Tarjan — recursion would overflow at 10k+ modules.
|
|
6
|
+
* Every module appears in exactly one component.
|
|
7
|
+
*/
|
|
8
|
+
declare const stronglyConnectedComponents: GraphComputation<readonly (readonly string[])[]>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/classifiers/path.d.ts
|
|
11
|
+
interface PathPattern {
|
|
12
|
+
/**
|
|
13
|
+
* Glob-lite, relative to the classifier `root` (itself under the config `sourceRoot`),
|
|
14
|
+
* anchored full-match: `:name` captures one segment as a tag, `*` matches within a
|
|
15
|
+
* segment, `**` across.
|
|
16
|
+
*/
|
|
17
|
+
pattern: string;
|
|
18
|
+
/** Literal tags, merged over the captures. */
|
|
19
|
+
tags?: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Constrains captured values. A capture outside its allow-list makes the pattern NOT
|
|
22
|
+
* match, so the next pattern is tried — this is how unknown top-level folders stay
|
|
23
|
+
* untagged (and therefore ignored by every rule) instead of inventing layers.
|
|
24
|
+
*/
|
|
25
|
+
only?: Record<string, readonly string[]>;
|
|
26
|
+
}
|
|
27
|
+
interface PathClassifierOptions {
|
|
28
|
+
name?: string;
|
|
29
|
+
/** Directory the patterns are relative to, itself relative to the config `sourceRoot`. Default ".". */
|
|
30
|
+
root?: string;
|
|
31
|
+
/** First match wins. */
|
|
32
|
+
patterns: PathPattern[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Declarative path→tag mapping. Every built-in preset is built on this, and it is the
|
|
36
|
+
* supported way to describe a custom architecture without writing a classify function.
|
|
37
|
+
*/
|
|
38
|
+
declare function pathClassifier(opts: PathClassifierOptions): Classifier;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/contracts/reporter.d.ts
|
|
41
|
+
/**
|
|
42
|
+
* Where a reporter's output goes.
|
|
43
|
+
*
|
|
44
|
+
* `"stdout"` and `"stderr"` are the two every environment has; anything else is a file
|
|
45
|
+
* path, which only a host with a filesystem can honour. A reporter never decides this —
|
|
46
|
+
* it writes to the sink it is handed.
|
|
47
|
+
*/
|
|
48
|
+
type OutputDestination = "stdout" | "stderr" | (string & {});
|
|
49
|
+
interface OutputSink {
|
|
50
|
+
write(text: string): void;
|
|
51
|
+
/** Flushed and awaited before the run's result is acted on. */
|
|
52
|
+
close?(): void | Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Opens destinations. The seam that lets the same built-in reporter write to a terminal,
|
|
56
|
+
* to stderr, or to `archwall.sarif` without knowing which.
|
|
57
|
+
*
|
|
58
|
+
* Per-reporter, so that a machine-readable document and a human summary in the same run
|
|
59
|
+
* never share a stream.
|
|
60
|
+
*/
|
|
61
|
+
interface ReporterIO {
|
|
62
|
+
open(destination: OutputDestination): OutputSink;
|
|
63
|
+
}
|
|
64
|
+
interface RunInfo {
|
|
65
|
+
/**
|
|
66
|
+
* Unique per analysis. In watch mode one reporter instance may see many runs, and
|
|
67
|
+
* without a way to tell them apart any per-run state it keeps grows forever. A custom
|
|
68
|
+
* reporter that accumulates anything should key it on this and drop the previous run's.
|
|
69
|
+
*/
|
|
70
|
+
runId: string;
|
|
71
|
+
host: HostInfo;
|
|
72
|
+
startedAt: number;
|
|
73
|
+
/** Absolute repository root, so a reporter can relativize before its first output. */
|
|
74
|
+
repoRoot: string;
|
|
75
|
+
}
|
|
76
|
+
interface AnalysisStats {
|
|
77
|
+
moduleCount: number;
|
|
78
|
+
edgeCount: number;
|
|
79
|
+
durationMs: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* What happened to one configured rule instance in this run.
|
|
83
|
+
*
|
|
84
|
+
* Without it there is no way to answer "did my rule actually run?" — the question behind
|
|
85
|
+
* every report of the tool being silently wrong. It is also what lets a reporter emit rule
|
|
86
|
+
* metadata for rules that produced no violations, which SARIF's `tool.driver.rules` wants.
|
|
87
|
+
*/
|
|
88
|
+
interface RuleRunInfo {
|
|
89
|
+
/** Instance id — what `overrides` matches and what violations report. */
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
docsUrl?: string;
|
|
94
|
+
severity: Severity;
|
|
95
|
+
/**
|
|
96
|
+
* `ran` — checked, whether or not it found anything.
|
|
97
|
+
* `skipped` — the host could not provide capabilities it requires.
|
|
98
|
+
* `failed` — it threw; see the matching `rule-failed` diagnostic.
|
|
99
|
+
*
|
|
100
|
+
* Rules dropped for invalid options or invalid configuration never reach the engine and
|
|
101
|
+
* so are absent here; they appear as diagnostics.
|
|
102
|
+
*/
|
|
103
|
+
status: "ran" | "skipped" | "failed";
|
|
104
|
+
/** Violations this instance produced. */
|
|
105
|
+
violations: number;
|
|
106
|
+
durationMs: number;
|
|
107
|
+
/** Present when `status: "skipped"`. */
|
|
108
|
+
missingCapabilities?: readonly Capability[];
|
|
109
|
+
/** Present when the rule is deprecated; mirrors the `rule-deprecated` diagnostic. */
|
|
110
|
+
deprecated?: boolean;
|
|
111
|
+
}
|
|
112
|
+
interface AnalysisResult {
|
|
113
|
+
/** Deterministically ordered; see `compareViolations`. */
|
|
114
|
+
violations: readonly Violation[];
|
|
115
|
+
/** Everything that is not a violation: skipped rules, crashed rules, config problems. */
|
|
116
|
+
diagnostics: readonly Diagnostic[];
|
|
117
|
+
stats: AnalysisStats;
|
|
118
|
+
/** Every rule instance the engine saw, in configuration order. */
|
|
119
|
+
rules: readonly RuleRunInfo[];
|
|
120
|
+
host: HostInfo;
|
|
121
|
+
delivery: GraphDelivery;
|
|
122
|
+
/**
|
|
123
|
+
* Absolute repository root. Reporters need it to emit repo-relative paths, and without
|
|
124
|
+
* it correct SARIF is impossible: `artifactLocation.uri` must be repo-relative or GitHub
|
|
125
|
+
* code scanning cannot associate a result with a file.
|
|
126
|
+
*/
|
|
127
|
+
repoRoot: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Two hooks, both batch. There is deliberately no per-violation streaming hook
|
|
131
|
+
*/
|
|
132
|
+
interface Reporter {
|
|
133
|
+
name: string;
|
|
134
|
+
/**
|
|
135
|
+
* Called before the engine runs. The place to reset per-run state, which matters because
|
|
136
|
+
* one reporter instance can outlive many runs in watch mode.
|
|
137
|
+
*/
|
|
138
|
+
onRunStart?(info: RunInfo): void | Promise<void>;
|
|
139
|
+
/** Awaited, so a reporter may write a file or flush a socket. */
|
|
140
|
+
onRunEnd(result: AnalysisResult): void | Promise<void>;
|
|
141
|
+
}
|
|
142
|
+
declare function defineReporter(reporter: Reporter): Reporter;
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/contracts/standard-schema.d.ts
|
|
145
|
+
/**
|
|
146
|
+
* Vendored Standard Schema v1 interface (standardschema.dev) so zod/valibot/arktype
|
|
147
|
+
* schemas type-check as rule option schemas without core taking a dependency.
|
|
148
|
+
*/
|
|
149
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
150
|
+
readonly "~standard": {
|
|
151
|
+
readonly version: 1;
|
|
152
|
+
readonly vendor: string;
|
|
153
|
+
readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
type StandardSchemaResult<Output> = {
|
|
157
|
+
value: Output;
|
|
158
|
+
issues?: undefined;
|
|
159
|
+
} | {
|
|
160
|
+
issues: ReadonlyArray<StandardSchemaIssue>;
|
|
161
|
+
};
|
|
162
|
+
interface StandardSchemaIssue {
|
|
163
|
+
message: string;
|
|
164
|
+
path?: ReadonlyArray<PropertyKey | {
|
|
165
|
+
key: PropertyKey;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/contracts/rule.d.ts
|
|
170
|
+
/**
|
|
171
|
+
* Marks a rule, or one of its options, as on the way out.
|
|
172
|
+
*
|
|
173
|
+
* One optional field, and the only thing standing between the project and a choice
|
|
174
|
+
* between "never rename anything" and "break everyone". The engine turns it into a
|
|
175
|
+
* `rule-deprecated` diagnostic when a deprecated rule is configured.
|
|
176
|
+
*/
|
|
177
|
+
interface RuleDeprecation {
|
|
178
|
+
/** Version in which the deprecation was announced. */
|
|
179
|
+
since: string;
|
|
180
|
+
/** Instance id or rule name to migrate to, when there is a direct replacement. */
|
|
181
|
+
replacedBy?: string;
|
|
182
|
+
/** Why, and what to do instead, when `replacedBy` alone does not say it. */
|
|
183
|
+
reason?: string;
|
|
184
|
+
/** Option names that are deprecated, when the rule itself is not. */
|
|
185
|
+
options?: Record<string, string>;
|
|
186
|
+
}
|
|
187
|
+
interface RuleMeta<Options> {
|
|
188
|
+
name: string;
|
|
189
|
+
description: string;
|
|
190
|
+
docsUrl?: string;
|
|
191
|
+
optionsSchema?: StandardSchemaV1<unknown, Options>;
|
|
192
|
+
defaultSeverity: Severity;
|
|
193
|
+
requiredCapabilities?: Capability[];
|
|
194
|
+
/**
|
|
195
|
+
* `messageId` → template, with `{placeholder}` interpolation from the reported `data`.
|
|
196
|
+
*
|
|
197
|
+
* Rules report an id and a data bag rather than a finished sentence, so the wording stays
|
|
198
|
+
* a property of the rule's *metadata*: retargetable per instance via
|
|
199
|
+
* `ConfiguredRule.message`, translatable, and machine-groupable.
|
|
200
|
+
*/
|
|
201
|
+
messages?: Record<string, string>;
|
|
202
|
+
/** Part of the curated set a "recommended" preset would enable. */
|
|
203
|
+
recommended?: boolean;
|
|
204
|
+
deprecated?: RuleDeprecation;
|
|
205
|
+
}
|
|
206
|
+
interface RuleContext<Options> {
|
|
207
|
+
options: Options;
|
|
208
|
+
graph: GraphQuery;
|
|
209
|
+
/**
|
|
210
|
+
* Absolute source root. The base for any path *pattern* a rule matches against, so that
|
|
211
|
+
* rule options read the same way as classifier patterns and `include`/`exclude`.
|
|
212
|
+
*/
|
|
213
|
+
sourceRoot: string;
|
|
214
|
+
/**
|
|
215
|
+
* Absolute repository root. For paths a rule puts in front of a human or another tool;
|
|
216
|
+
* reporters relativize against this.
|
|
217
|
+
*/
|
|
218
|
+
repoRoot: string;
|
|
219
|
+
/**
|
|
220
|
+
* A file's path relative to {@link sourceRoot}, forward-slashed, or null when it lies
|
|
221
|
+
* outside. Every rule that matches paths needs exactly this, and hand-rolling it is how
|
|
222
|
+
* six copies with three different edge-case behaviours came to exist.
|
|
223
|
+
*/
|
|
224
|
+
relative(file: string): string | null;
|
|
225
|
+
/**
|
|
226
|
+
* A module id as a human should read it — `src/domain/rules.ts`, `react`, `node:fs`.
|
|
227
|
+
*
|
|
228
|
+
* Use it for anything that goes into a message's `data`. A canonical {@link ModuleId} is
|
|
229
|
+
* scheme-prefixed, and a rule that interpolates one raw puts `file:src/a.ts` in front of a
|
|
230
|
+
* user.
|
|
231
|
+
*/
|
|
232
|
+
display(id: ModuleId): string;
|
|
233
|
+
/**
|
|
234
|
+
* Shared memoized graph computations, one evaluation per run.
|
|
235
|
+
*
|
|
236
|
+
* Scoped like `graph`: a computation requested by a scoped rule is evaluated over that rule's
|
|
237
|
+
* slice.
|
|
238
|
+
*/
|
|
239
|
+
compute<T>(computation: GraphComputation<T>): T;
|
|
240
|
+
report(violation: ViolationInput): void;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* What a rule wants to look at, declared rather than fetched.
|
|
244
|
+
*
|
|
245
|
+
* The engine owns the traversal, so one slice of the graph is evaluated once for every rule
|
|
246
|
+
* that wants it, and the engine knows which rules a given edge can affect — the
|
|
247
|
+
* prerequisite for incremental validation.
|
|
248
|
+
*
|
|
249
|
+
* `check` remains for rules that genuinely need the whole graph at once (cycle detection,
|
|
250
|
+
* reachability). It is the exception, not the interface.
|
|
251
|
+
*/
|
|
252
|
+
interface RuleVisitors<Options> {
|
|
253
|
+
edges?: {
|
|
254
|
+
/**
|
|
255
|
+
* Narrows the edges `visit` receives. A function of the rule's options, because the
|
|
256
|
+
* interesting filters depend on them (`crossing: options.tagKey`).
|
|
257
|
+
*/
|
|
258
|
+
filter?: (options: Options) => EdgeFilter | undefined;
|
|
259
|
+
visit(edge: Edge, ctx: RuleContext<Options>): void;
|
|
260
|
+
};
|
|
261
|
+
modules?: {
|
|
262
|
+
filter?: (options: Options) => ModuleFilter | undefined;
|
|
263
|
+
visit(module: ModuleNode, ctx: RuleContext<Options>): void;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
interface Rule<Options = unknown> {
|
|
267
|
+
meta: RuleMeta<Options>;
|
|
268
|
+
/** Declared interest; the engine drives the traversal. Preferred. */
|
|
269
|
+
visits?: RuleVisitors<Options>;
|
|
270
|
+
/** Whole-graph escape hatch, for rules that cannot be expressed as a traversal. */
|
|
271
|
+
check?(ctx: RuleContext<Options>): void | Promise<void>;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Instance settings, deliberately a SEPARATE bag from the rule's options, so that no
|
|
275
|
+
* option name is reserved across every rule that will ever exist.
|
|
276
|
+
*/
|
|
277
|
+
interface RuleSettings {
|
|
278
|
+
id?: string;
|
|
279
|
+
severity?: Severity | "off";
|
|
280
|
+
scope?: RuleScope;
|
|
281
|
+
/**
|
|
282
|
+
* Retargets this instance's wording: one template when the rule has a single message, or
|
|
283
|
+
* `messageId` → template.
|
|
284
|
+
*/
|
|
285
|
+
message?: string | Record<string, string>;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Restricts one rule instance to part of the graph.
|
|
289
|
+
*
|
|
290
|
+
* This is what makes a monorepo expressible: "FSD under `apps/web`, layered under
|
|
291
|
+
* `services/api`" is two instances of two rules with two scopes, in ONE config and ONE
|
|
292
|
+
* pass. Applied by the ENGINE, by narrowing the `GraphQuery` a rule receives, so every
|
|
293
|
+
* rule that will ever be written inherits scoping for free and none of them has to know it
|
|
294
|
+
* exists.
|
|
295
|
+
*/
|
|
296
|
+
interface RuleScope {
|
|
297
|
+
/**
|
|
298
|
+
* Glob-lite paths relative to `sourceRoot`, matched against module files. Default: the
|
|
299
|
+
* whole project.
|
|
300
|
+
*/
|
|
301
|
+
include?: string[];
|
|
302
|
+
/** Glob-lite paths to remove from `include`. */
|
|
303
|
+
exclude?: string[];
|
|
304
|
+
/** Only modules carrying ALL of these tags. */
|
|
305
|
+
tag?: Record<string, string>;
|
|
306
|
+
}
|
|
307
|
+
/** A rule that is also a function returning a configured instance of itself. */
|
|
308
|
+
interface CallableRule<Options = unknown> extends Rule<Options> {
|
|
309
|
+
(options?: Partial<Options>, settings?: RuleSettings): ConfiguredRule<Options>;
|
|
310
|
+
}
|
|
311
|
+
declare function defineRule<O>(rule: Rule<O>): CallableRule<O>;
|
|
312
|
+
/**
|
|
313
|
+
* A configured rule instance whose option type is not known at the use site — what a
|
|
314
|
+
* `Preset`, a `UserConfig`, and the engine all hold.
|
|
315
|
+
*
|
|
316
|
+
* `any` rather than `unknown` deliberately: `RuleVisitors` puts `Options` in contravariant
|
|
317
|
+
* position (`filter(options)`, `visit(item, ctx)`), which makes `ConfiguredRule` invariant
|
|
318
|
+
* in `Options`, so `ConfiguredRule<unknown>` would reject every concrete rule there is.
|
|
319
|
+
*/
|
|
320
|
+
type AnyConfiguredRule = ConfiguredRule<any>;
|
|
321
|
+
interface ConfiguredRule<O = unknown> {
|
|
322
|
+
rule: Rule<O>;
|
|
323
|
+
/**
|
|
324
|
+
* Merge key and `overrides` key. Inside a preset it defaults to
|
|
325
|
+
* `<preset>/<rule.meta.name>`; elsewhere to `rule.meta.name`. Set it explicitly to carry
|
|
326
|
+
* two instances of the same rule in one preset.
|
|
327
|
+
*/
|
|
328
|
+
id?: string;
|
|
329
|
+
options?: Partial<O>;
|
|
330
|
+
severity?: Severity | "off";
|
|
331
|
+
/** Restricts this instance to part of the graph; applied by the engine. */
|
|
332
|
+
scope?: RuleScope;
|
|
333
|
+
/** Per-instance message templates; see {@link RuleSettings.message}. */
|
|
334
|
+
message?: string | Record<string, string>;
|
|
335
|
+
}
|
|
336
|
+
declare function configureRule<O>(rule: Rule<O>, options?: Partial<O>, settings?: RuleSettings): ConfiguredRule<O>;
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/contracts/preset.d.ts
|
|
339
|
+
/**
|
|
340
|
+
* Everything a third party can ship as one installable unit.
|
|
341
|
+
*
|
|
342
|
+
* There is deliberately no separate `Plugin` type above this one. A second, near-identical
|
|
343
|
+
* bundle would mean everyone has to learn which of the two they need and every downstream
|
|
344
|
+
* API has to accept both; widening the one bundle that already exists costs a few optional
|
|
345
|
+
* fields and no new vocabulary.
|
|
346
|
+
*
|
|
347
|
+
* The optional fields are declared up front on purpose: `Preset` is promised as stable, and
|
|
348
|
+
* adding a field to a stable type is a breaking change for anyone who wrote
|
|
349
|
+
* `satisfies Preset`.
|
|
350
|
+
*/
|
|
351
|
+
/**
|
|
352
|
+
* Descriptive facts about a preset. Nothing in core reads these yet — they exist now because
|
|
353
|
+
* `Preset` is promised as stable, so this is the last moment at which adding them is free.
|
|
354
|
+
*
|
|
355
|
+
* The index signature is the load-bearing part: with it, every future named field is additive
|
|
356
|
+
* and a third party can carry its own facts without waiting for core. Without it, this type
|
|
357
|
+
* would have exactly the problem it exists to solve.
|
|
358
|
+
*/
|
|
359
|
+
interface PresetMeta {
|
|
360
|
+
/** The preset package's version, for reporters and bug reports. */
|
|
361
|
+
version?: string;
|
|
362
|
+
description?: string;
|
|
363
|
+
docsUrl?: string;
|
|
364
|
+
[key: string]: unknown;
|
|
365
|
+
}
|
|
366
|
+
interface Preset {
|
|
367
|
+
name: string;
|
|
368
|
+
classifiers: Classifier[];
|
|
369
|
+
rules: AnyConfiguredRule[];
|
|
370
|
+
/** See {@link PresetMeta}. Purely descriptive; it never affects analysis. */
|
|
371
|
+
meta?: PresetMeta;
|
|
372
|
+
/**
|
|
373
|
+
* Passes that enrich the graph before classification — the slot a TypeScript type-edge
|
|
374
|
+
* enricher, or any other "add facts the bundler didn't give us" pass, lives in.
|
|
375
|
+
*/
|
|
376
|
+
transforms?: GraphTransform[];
|
|
377
|
+
/**
|
|
378
|
+
* Reporters the preset contributes. Appended to whatever the user configured rather
|
|
379
|
+
* than replacing it: a preset that ships an uploader should not silently remove the
|
|
380
|
+
* console output the user is reading.
|
|
381
|
+
*/
|
|
382
|
+
reporters?: Reporter[];
|
|
383
|
+
}
|
|
384
|
+
declare function definePreset<A extends unknown[]>(fn: (...args: A) => Preset): (...args: A) => Preset;
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/reporters/resolve.d.ts
|
|
387
|
+
type BuiltinReporterName = "console" | "json" | "sarif";
|
|
388
|
+
declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
|
|
389
|
+
declare function isBuiltinReporterName(name: string): name is BuiltinReporterName;
|
|
390
|
+
/**
|
|
391
|
+
* A reporter plus where its output goes.
|
|
392
|
+
*
|
|
393
|
+
* The object form exists so that `sarif` can write `archwall.sarif` while `console` keeps
|
|
394
|
+
* the terminal, in one run. Without it every reporter shares one stream and machine-readable
|
|
395
|
+
* output is contaminated by human-readable output.
|
|
396
|
+
*/
|
|
397
|
+
interface ReporterOutputSpec {
|
|
398
|
+
reporter: BuiltinReporterName | Reporter;
|
|
399
|
+
/** Default `"stdout"`. Also accepts `"stderr"` or a file path. */
|
|
400
|
+
output?: OutputDestination;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* `string` is accepted so a config file can name a third-party reporter
|
|
404
|
+
* (`"archwall-reporter-teamcity"`). Those are resolved to objects before reaching the
|
|
405
|
+
* engine; a string that survives to here is one nothing could load.
|
|
406
|
+
*/
|
|
407
|
+
type ReporterSpec = BuiltinReporterName | Reporter | ReporterOutputSpec | (string & {});
|
|
408
|
+
interface ResolvedReporters {
|
|
409
|
+
reporters: readonly Reporter[];
|
|
410
|
+
/** Closes every sink this opened. Awaited after `onRunEnd`, before acting on the result. */
|
|
411
|
+
close(): Promise<void>;
|
|
412
|
+
}
|
|
413
|
+
declare function resolveReporters(specs: readonly ReporterSpec[], io?: ReporterIO): ResolvedReporters;
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/config.d.ts
|
|
416
|
+
type FailOn = "error" | "warn" | "never";
|
|
417
|
+
/**
|
|
418
|
+
* Which diagnostics are severe enough to fail the run, independently of violations.
|
|
419
|
+
*
|
|
420
|
+
* `ruleFailed` and `invalidConfig` default to true and should stay that way: a rule that
|
|
421
|
+
* throws, and a rule dropped because its configuration was invalid, both produce no results
|
|
422
|
+
* — so a run in which either happened is a run that did not check what you asked it to. An
|
|
423
|
+
* enforcement tool that passes green when a rule crashes is not an enforcement tool.
|
|
424
|
+
*/
|
|
425
|
+
interface FailOnDiagnostics {
|
|
426
|
+
/** A rule threw. Default true. */
|
|
427
|
+
ruleFailed?: boolean;
|
|
428
|
+
/** A rule was skipped for missing host capabilities. Default false. */
|
|
429
|
+
ruleSkipped?: boolean;
|
|
430
|
+
/** Classification tagged nothing, or the boundary matched nothing. Default false. */
|
|
431
|
+
emptyAnalysis?: boolean;
|
|
432
|
+
/**
|
|
433
|
+
* A rule's `scope` resolved to zero modules. Default false.
|
|
434
|
+
*
|
|
435
|
+
* Its own switch rather than part of `emptyAnalysis`: "the run looked at nothing" and "this
|
|
436
|
+
* one rule looked at nothing" are different failures, and a monorepo where some packages
|
|
437
|
+
* legitimately have no modules yet wants to tolerate the second while still gating the first.
|
|
438
|
+
*/
|
|
439
|
+
emptyScope?: boolean;
|
|
440
|
+
/** A rule's options failed its schema, so the rule did not run. Default true. */
|
|
441
|
+
invalidOptions?: boolean;
|
|
442
|
+
/** The configuration itself is wrong and something was dropped. Default true. */
|
|
443
|
+
invalidConfig?: boolean;
|
|
444
|
+
/** A configured rule is deprecated. Default false. */
|
|
445
|
+
deprecated?: boolean;
|
|
446
|
+
}
|
|
447
|
+
interface ResolvedFailOnDiagnostics {
|
|
448
|
+
ruleFailed: boolean;
|
|
449
|
+
ruleSkipped: boolean;
|
|
450
|
+
emptyAnalysis: boolean;
|
|
451
|
+
emptyScope: boolean;
|
|
452
|
+
invalidOptions: boolean;
|
|
453
|
+
invalidConfig: boolean;
|
|
454
|
+
deprecated: boolean;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Which diagnostic codes each `failOnDiagnostics` switch governs, and whether it is on by
|
|
458
|
+
* default. The single source of truth for both.
|
|
459
|
+
*
|
|
460
|
+
* One table because there used to be three: the code list lived in `@archwall/integration-kit`,
|
|
461
|
+
* the defaults lived in `resolveConfig` below, and a second copy of the defaults lived beside
|
|
462
|
+
* the code list. Nothing linked them, so adding a gate meant remembering all three, and
|
|
463
|
+
* forgetting the third produced a switch that resolved correctly and then gated nothing.
|
|
464
|
+
*
|
|
465
|
+
* The `satisfies` is what keeps it honest: a key added to {@link ResolvedFailOnDiagnostics}
|
|
466
|
+
* and not here is a compile error, and vice versa.
|
|
467
|
+
*/
|
|
468
|
+
declare const DIAGNOSTIC_GATES: {
|
|
469
|
+
readonly ruleFailed: {
|
|
470
|
+
readonly codes: readonly ["rule-failed"];
|
|
471
|
+
readonly default: true;
|
|
472
|
+
};
|
|
473
|
+
readonly ruleSkipped: {
|
|
474
|
+
readonly codes: readonly ["rule-skipped"];
|
|
475
|
+
readonly default: false;
|
|
476
|
+
};
|
|
477
|
+
readonly emptyAnalysis: {
|
|
478
|
+
readonly codes: readonly ["no-modules-classified", "empty-project"];
|
|
479
|
+
readonly default: false;
|
|
480
|
+
};
|
|
481
|
+
readonly emptyScope: {
|
|
482
|
+
readonly codes: readonly ["empty-scope"];
|
|
483
|
+
readonly default: false;
|
|
484
|
+
};
|
|
485
|
+
readonly invalidOptions: {
|
|
486
|
+
readonly codes: readonly ["invalid-rule-options"];
|
|
487
|
+
readonly default: true;
|
|
488
|
+
};
|
|
489
|
+
readonly invalidConfig: {
|
|
490
|
+
readonly codes: readonly ["invalid-config"];
|
|
491
|
+
readonly default: true;
|
|
492
|
+
};
|
|
493
|
+
readonly deprecated: {
|
|
494
|
+
readonly codes: readonly ["rule-deprecated"];
|
|
495
|
+
readonly default: false;
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Applies {@link DIAGNOSTIC_GATES}' defaults to whatever the user left unset.
|
|
500
|
+
*
|
|
501
|
+
* Spelled out key by key rather than mapped over `GATE_KEYS`, so that adding a gate is a
|
|
502
|
+
* compile error here until it is handled. The values still come from the one table; only the
|
|
503
|
+
* exhaustiveness is restated, and restating it is the thing being bought.
|
|
504
|
+
*/
|
|
505
|
+
declare function resolveFailOnDiagnostics(user: FailOnDiagnostics | undefined): ResolvedFailOnDiagnostics;
|
|
506
|
+
/** The diagnostic codes that should fail a run, given the resolved gates. */
|
|
507
|
+
declare function failingDiagnosticCodes(gates: ResolvedFailOnDiagnostics): Set<DiagnosticCode>;
|
|
508
|
+
/**
|
|
509
|
+
* Retune one rule instance. The shorthand form sets severity only; the object form can also
|
|
510
|
+
* patch options, scope, and wording.
|
|
511
|
+
*
|
|
512
|
+
* Options merge by ONE policy, the same one used everywhere: **top-level keys replace, and
|
|
513
|
+
* arrays are replaced wholesale, never concatenated.**
|
|
514
|
+
*/
|
|
515
|
+
type RuleOverride = Severity | "off" | {
|
|
516
|
+
severity?: Severity | "off";
|
|
517
|
+
options?: Record<string, unknown>;
|
|
518
|
+
scope?: RuleScope;
|
|
519
|
+
message?: string | Record<string, string>;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* A preset, or the name of a package exporting one.
|
|
523
|
+
*
|
|
524
|
+
* The string form is what makes a plugin ecosystem possible: without it a preset can only
|
|
525
|
+
* be `import`ed, which forecloses JSON/YAML configuration and any `--preset` flag forever.
|
|
526
|
+
* Strings are resolved by the config loader (`@archwall/integration-kit`), which is the
|
|
527
|
+
* layer that has a module resolver; one that reaches {@link resolveConfig} unresolved is
|
|
528
|
+
* reported as a configuration error rather than silently ignored.
|
|
529
|
+
*
|
|
530
|
+
* `["@acme/preset", { … }]` calls the package's default export with those options.
|
|
531
|
+
*/
|
|
532
|
+
type PresetSpec = Preset | string | readonly [string, Record<string, unknown>?];
|
|
533
|
+
/** A configured rule, or the name of a package/built-in exporting one. See {@link PresetSpec}. */
|
|
534
|
+
type RuleSpec = AnyConfiguredRule | string | readonly [string, Record<string, unknown>?, RuleSettings?];
|
|
535
|
+
interface UserConfig {
|
|
536
|
+
/**
|
|
537
|
+
* Configurations to inherit from, nearest-last: a later entry wins over an earlier one,
|
|
538
|
+
* and this config wins over all of them.
|
|
539
|
+
*
|
|
540
|
+
* Arrays (`presets`, `rules`, `classifiers`, `transforms`, `reporters`, `exclude`)
|
|
541
|
+
* CONCATENATE base-first, because rules already merge by instance id downstream and
|
|
542
|
+
* `overrides` already exists for retuning. Scalars replace. `overrides` merges key-wise.
|
|
543
|
+
*
|
|
544
|
+
* This is the only way to ship an organisation-wide configuration: a `Preset` cannot set
|
|
545
|
+
* `failOn`, `include`, `exclude`, `repoRoot`, or `reporters`, so without `extends` a
|
|
546
|
+
* shared config is a preset plus a README telling every repository to copy twenty lines.
|
|
547
|
+
*/
|
|
548
|
+
extends?: string | string[];
|
|
549
|
+
/**
|
|
550
|
+
* Where the *repository* starts, relative to the config file / cwd. Default ".".
|
|
551
|
+
*
|
|
552
|
+
* The base for everything that leaves the process — reporter output, SARIF
|
|
553
|
+
* `artifactLocation.uri`, violation fingerprints — so it must be the path a checkout is
|
|
554
|
+
* rooted at, not the path your sources happen to live under.
|
|
555
|
+
*/
|
|
556
|
+
repoRoot?: string;
|
|
557
|
+
/**
|
|
558
|
+
* Where the *sources* start, relative to {@link repoRoot}. Default ".".
|
|
559
|
+
*
|
|
560
|
+
* The base for `include`/`exclude` matching and for classifier patterns — the tree whose
|
|
561
|
+
* shape your architecture is described in. This is the one that is usually `"src"`.
|
|
562
|
+
*/
|
|
563
|
+
sourceRoot?: string;
|
|
564
|
+
include?: string[];
|
|
565
|
+
/**
|
|
566
|
+
* Patterns ADDED to the defaults (`node_modules`, `*.test.*`, `*.spec.*`), not a
|
|
567
|
+
* replacement for them. Use {@link excludeDefaults} to opt out deliberately.
|
|
568
|
+
*/
|
|
569
|
+
exclude?: string[];
|
|
570
|
+
/** Set false to drop the built-in `exclude` defaults entirely. */
|
|
571
|
+
excludeDefaults?: boolean;
|
|
572
|
+
presets?: PresetSpec[];
|
|
573
|
+
/** Appended after preset classifiers. */
|
|
574
|
+
classifiers?: Classifier[];
|
|
575
|
+
/** Appended after preset transforms; run between the project boundary and classification. */
|
|
576
|
+
transforms?: GraphTransform[];
|
|
577
|
+
/** Merged after preset rules (last-writer-wins, keyed by rule instance id). */
|
|
578
|
+
rules?: RuleSpec[];
|
|
579
|
+
/**
|
|
580
|
+
* Retunes rule instances; ALWAYS wins over presets and rules. Keys are an exact instance
|
|
581
|
+
* id ("fsd/public-api"), a bare rule name (every instance of it), or a glob ("fsd/*").
|
|
582
|
+
* A key that matches no rule is an error, not a silent no-op.
|
|
583
|
+
*/
|
|
584
|
+
overrides?: Record<string, RuleOverride>;
|
|
585
|
+
/**
|
|
586
|
+
* Built-ins by name, customs by object, third-party by package name, and
|
|
587
|
+
* `{ reporter, output }` to send one somewhere other than stdout. Default ["console"].
|
|
588
|
+
*/
|
|
589
|
+
reporters?: ReporterSpec[];
|
|
590
|
+
/** Which VIOLATION severity gates the run. `info` findings never fail it. */
|
|
591
|
+
failOn?: FailOn;
|
|
592
|
+
/** Which DIAGNOSTICS gate the run, regardless of `failOn`. */
|
|
593
|
+
failOnDiagnostics?: FailOnDiagnostics;
|
|
594
|
+
}
|
|
595
|
+
declare function defineConfig(config: UserConfig): UserConfig;
|
|
596
|
+
interface ResolvedRule {
|
|
597
|
+
rule: Rule<any>;
|
|
598
|
+
/** Instance id; what violations report and what `overrides` keys match. */
|
|
599
|
+
id: string;
|
|
600
|
+
options: unknown;
|
|
601
|
+
severity: Severity;
|
|
602
|
+
/** Narrows the graph this instance sees; applied by the engine, never by the rule. */
|
|
603
|
+
scope?: RuleScope;
|
|
604
|
+
/** Per-instance message templates. */
|
|
605
|
+
message?: string | Record<string, string>;
|
|
606
|
+
}
|
|
607
|
+
interface ResolvedConfig {
|
|
608
|
+
/** Absolute. Base for reported paths and fingerprints. */
|
|
609
|
+
repoRoot: string;
|
|
610
|
+
/** Absolute, at or below {@link repoRoot}. Base for the boundary and classifiers. */
|
|
611
|
+
sourceRoot: string;
|
|
612
|
+
include: string[];
|
|
613
|
+
exclude: string[];
|
|
614
|
+
classifiers: readonly Classifier[];
|
|
615
|
+
transforms: readonly GraphTransform[];
|
|
616
|
+
rules: readonly ResolvedRule[];
|
|
617
|
+
/** Reporter instantiation is deferred to the run edge (resolveReporters). */
|
|
618
|
+
reporterSpecs: readonly ReporterSpec[];
|
|
619
|
+
failOn: FailOn;
|
|
620
|
+
failOnDiagnostics: ResolvedFailOnDiagnostics;
|
|
621
|
+
/**
|
|
622
|
+
* Everything wrong with the configuration itself, found before any graph work.
|
|
623
|
+
*
|
|
624
|
+
* Reported rather than thrown: a throw inside a bundler's `buildEnd` produces a stack
|
|
625
|
+
* trace and destroys every other finding in the run. One mistyped `overrides` key costs
|
|
626
|
+
* you that key, not the analysis — and `failOnDiagnostics.invalidConfig` still fails the
|
|
627
|
+
* run.
|
|
628
|
+
*/
|
|
629
|
+
diagnostics: readonly Diagnostic[];
|
|
630
|
+
}
|
|
631
|
+
declare function resolveConfig(user: UserConfig, opts?: {
|
|
632
|
+
cwd?: string;
|
|
633
|
+
}): ResolvedConfig;
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/engine/analyze.d.ts
|
|
636
|
+
/**
|
|
637
|
+
* The engine: prepare the graph (boundary → transforms → classify), then check it.
|
|
638
|
+
*
|
|
639
|
+
* Pure — no I/O, no reporter calls; reporters are driven by the run edge (integration-kit).
|
|
640
|
+
*/
|
|
641
|
+
declare function analyze(graph: ProjectGraph, config: ResolvedConfig): Promise<AnalysisResult>;
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region src/errors.d.ts
|
|
644
|
+
declare class ArchWallError extends Error {
|
|
645
|
+
constructor(message: string);
|
|
646
|
+
}
|
|
647
|
+
declare class IrVersionMismatchError extends ArchWallError {
|
|
648
|
+
readonly graphVersion: string;
|
|
649
|
+
readonly coreVersion: string;
|
|
650
|
+
constructor(graphVersion: string, coreVersion: string);
|
|
651
|
+
}
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/match.d.ts
|
|
654
|
+
/**
|
|
655
|
+
* Anchored full-match test.
|
|
656
|
+
*
|
|
657
|
+
* `dot: true` so a pattern matches dotfiles without every caller remembering to say so —
|
|
658
|
+
* a rule that silently skips `.storybook/` is the kind of quiet gap this tool exists to
|
|
659
|
+
* prevent.
|
|
660
|
+
*/
|
|
661
|
+
declare function matchesPattern(value: string, pattern: string): boolean;
|
|
662
|
+
/**
|
|
663
|
+
* Anchored full-match returning the `:name` captures, or null when the pattern does not
|
|
664
|
+
* match. A pattern with no captures yields an empty object on match — callers must check
|
|
665
|
+
* for null rather than for emptiness.
|
|
666
|
+
*/
|
|
667
|
+
declare function matchCaptures(value: string, pattern: string): Record<string, string> | null;
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/reporters/console.d.ts
|
|
670
|
+
/**
|
|
671
|
+
* Console-only IO: the portable default.
|
|
672
|
+
*
|
|
673
|
+
* Core stays runnable wherever a graph can be built — browser playground, worker, edge
|
|
674
|
+
* runtime — so it cannot open files. A host with a filesystem supplies an IO that can
|
|
675
|
+
* (`@archwall/integration-kit` exports `nodeIO`); asking this one for a file is an error
|
|
676
|
+
* rather than a silent fallback to stdout, because a run that was told to write
|
|
677
|
+
* `archwall.sarif` and printed to the terminal instead has failed at its actual job.
|
|
678
|
+
*/
|
|
679
|
+
declare const defaultIO: ReporterIO;
|
|
680
|
+
/**
|
|
681
|
+
* Shared violation block format — also used by adapters when mapping violations into host
|
|
682
|
+
* diagnostics (error locality: anchored on the importer edge, resolution shown as
|
|
683
|
+
* explanation, never as the location).
|
|
684
|
+
*
|
|
685
|
+
* `repoRoot` makes every path repository-relative. Absolute paths are the right module
|
|
686
|
+
* identity inside a run and the wrong thing in every output.
|
|
687
|
+
*/
|
|
688
|
+
declare function formatViolation(v: Violation, repoRoot?: string): string;
|
|
689
|
+
/**
|
|
690
|
+
* Stateless: one pass over the finished result, in `onRunEnd`.
|
|
691
|
+
*
|
|
692
|
+
* There is no `onRunStart` and no per-run state to reset, which is what makes it safe for
|
|
693
|
+
* the run object to be memoized across watch rebuilds in the bundler adapters — a reporter
|
|
694
|
+
* that accumulated anything would grow for the life of the process.
|
|
695
|
+
*/
|
|
696
|
+
declare function consoleReporter(sink: OutputSink): Reporter;
|
|
697
|
+
//#endregion
|
|
698
|
+
//#region src/reporters/json.d.ts
|
|
699
|
+
declare function jsonReporter(sink: OutputSink): Reporter;
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/reporters/sarif.d.ts
|
|
702
|
+
declare function sarifReporter(sink: OutputSink): Reporter;
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/transforms/drop-self-edges.d.ts
|
|
705
|
+
/**
|
|
706
|
+
* Removes edges from a module to itself.
|
|
707
|
+
*
|
|
708
|
+
* This is a *semantic policy*, and it belongs in shared code a host opts into rather than
|
|
709
|
+
* inside one adapter: HMR instrumentation adds self-edges (React Fast Refresh makes every
|
|
710
|
+
* transformed component module import itself), and that reasoning is not Vite-specific —
|
|
711
|
+
* the moment another bundler's HMR does the same thing, an adapter-local fix has to be
|
|
712
|
+
* written a second time, and the two can then disagree.
|
|
713
|
+
*
|
|
714
|
+
* Deliberately NOT on by default: a genuine self-import is a real finding, and build mode
|
|
715
|
+
* sees the real graph. A host applies this only where it knows its own instrumentation
|
|
716
|
+
* created the edges.
|
|
717
|
+
*/
|
|
718
|
+
declare function dropSelfEdges(): GraphTransform;
|
|
719
|
+
//#endregion
|
|
720
|
+
export { type AnalysisResult, type AnalysisStats, ArchWallError, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, type CallableRule, type Capability, type Classifier, type ClassifierContext, type ConfiguredRule, DIAGNOSTIC_GATES, type Diagnostic, type DiagnosticCode, type DiagnosticSeverity, type Edge, type EdgeFilter, type EdgeKind, type EmptyScopeDetails, FINGERPRINT_SCHEME, FIRST_PARTY_KINDS, type FailOn, type FailOnDiagnostics, type GraphComputation, type GraphDelivery, type GraphMutation, GraphQuery, type GraphTransform, type HostInfo, IR_VERSION, IrVersionMismatchError, MODULE_ID_SCHEMES, type ModuleFilter, type ModuleId, type ModuleIdScheme, type ModuleKind, type ModuleNode, type ModuleSelection, type OutputDestination, type OutputSink, type PathClassifierOptions, type PathPattern, type Preset, type PresetMeta, type PresetSpec, ProjectGraph, type ProjectGraphInit, type Reporter, type ReporterIO, type ReporterOutputSpec, type ReporterSpec, type ResolvedConfig, type ResolvedFailOnDiagnostics, type ResolvedReporters, type ResolvedRule, type Rule, type RuleContext, type RuleDeprecation, type RuleMeta, type RuleOverride, type RuleRunInfo, type RuleScope, type RuleSettings, type RuleSkippedDetails, type RuleSpec, type RuleVisitors, type RunInfo, type Severity, type SeverityCounts, type SourceLocation, type StandardSchemaIssue, type StandardSchemaResult, type StandardSchemaV1, THIRD_PARTY_KINDS, type TagPatch, type TransformContext, type UserConfig, type Violation, type ViolationInput, type ViolationLocation, type WellKnownCapability, type WellKnownDiagnosticCode, type WellKnownEdgeKind, analyze, assertIrCompatible, compareViolations, configureRule, consoleReporter, countBySeverity, defaultIO, defineClassifier, defineConfig, defineGraphComputation, definePreset, defineReporter, defineRule, defineTransform, displayModuleId, dropSelfEdges, failingDiagnosticCodes, fingerprintOf, formatViolation, irMajor, isBuiltinReporterName, isFirstParty, isThirdParty, jsonReporter, locationsOf, matchCaptures, matchesPattern, parseModuleId, pathClassifier, primaryEdge, primaryModule, primarySourceLocation, renderMessage, resolveConfig, resolveFailOnDiagnostics, resolveReporters, sarifReporter, stronglyConnectedComponents };
|
|
721
|
+
//# sourceMappingURL=index.d.mts.map
|