@codependix/boundaries 0.0.3
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 +748 -0
- package/dist/src/index.d.ts +736 -0
- package/dist/src/index.js +509 -0
- package/package.json +65 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import { CodependixBoundaryRule } from '@codependix/configuration';
|
|
2
|
+
import { CodependixBoundarySelector } from '@codependix/configuration';
|
|
3
|
+
import { CodependixGraphType } from '@codependix/configuration';
|
|
4
|
+
import { CodependixProjectConfiguration } from '@codependix/configuration';
|
|
5
|
+
import { CodependixRunMode } from '@codependix/core';
|
|
6
|
+
import { ConfigurationService } from '@codependix/configuration';
|
|
7
|
+
import { MapCommandOptions } from '@codependix/configuration';
|
|
8
|
+
import { ModuleGraphService } from '@codependix/nestjs-modules';
|
|
9
|
+
import { NeighborhoodService } from '@codependix/nx-projects';
|
|
10
|
+
import { NestjsModuleGraph } from '@codependix/nestjs-modules';
|
|
11
|
+
import { NestjsProjectService } from '@codependix/nestjs-modules';
|
|
12
|
+
import { NxProject } from '@codependix/nx-projects';
|
|
13
|
+
import { NxProjectGraph } from '@codependix/nx-projects';
|
|
14
|
+
import { PythonImportGraph } from '@codependix/file-imports';
|
|
15
|
+
import { PythonService } from '@codependix/file-imports';
|
|
16
|
+
import { ResolvedCodependixConfiguration } from '@codependix/configuration';
|
|
17
|
+
import { TypescriptImportGraph } from '@codependix/file-imports';
|
|
18
|
+
import { TypescriptService } from '@codependix/file-imports';
|
|
19
|
+
import { WorkspaceGraph } from '@codependix/nx-projects';
|
|
20
|
+
import { WorkspaceGraphService } from '@codependix/nx-projects';
|
|
21
|
+
|
|
22
|
+
/** Wires rule evaluation together with selection, cycle finding, and reporting. */
|
|
23
|
+
export declare class BoundariesModule {
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Judges a built graph against the rules declared for its level.
|
|
28
|
+
*
|
|
29
|
+
* The whole package's public act: a `BoundaryGraph` and a list of rules go in,
|
|
30
|
+
* and the edges and cycles that break them come out. Nothing here knows how a
|
|
31
|
+
* graph was built, where a rule was written, or what a caller will do with a
|
|
32
|
+
* violation — which is what keeps the package a leaf, depending on nothing but
|
|
33
|
+
* `@codependix/configuration` for the rule shapes it reads.
|
|
34
|
+
*
|
|
35
|
+
* A rule that matches nothing is not an error. It reports no violation, the
|
|
36
|
+
* same as a rule everything satisfies: a workspace that has not yet grown the
|
|
37
|
+
* code a rule was written for should not be failed for it, and a rule kept
|
|
38
|
+
* around after the code it covered was deleted is a cleanup rather than a
|
|
39
|
+
* breakage.
|
|
40
|
+
*/
|
|
41
|
+
export declare class BoundariesService {
|
|
42
|
+
private readonly cyclesService;
|
|
43
|
+
private readonly selectorService;
|
|
44
|
+
constructor(cyclesService: BoundaryCyclesService, selectorService: BoundarySelectorService);
|
|
45
|
+
/** Turns one condemned edge into the violation reported for it. */
|
|
46
|
+
private buildAccessViolation;
|
|
47
|
+
/**
|
|
48
|
+
* The sentence a violation is reported as.
|
|
49
|
+
*
|
|
50
|
+
* A rule's own `message` is appended to the generated one rather than
|
|
51
|
+
* replacing it, so no wording a configuration can choose ever costs the
|
|
52
|
+
* report the two things it must always carry: the rule that fired, and both
|
|
53
|
+
* ends of what it fired on. The generated half says what happened, and the
|
|
54
|
+
* configured half says why it matters.
|
|
55
|
+
*/
|
|
56
|
+
private buildMessage;
|
|
57
|
+
/**
|
|
58
|
+
* Reports every edge an `allow` or `forbid` rule condemns.
|
|
59
|
+
*
|
|
60
|
+
* The two kinds differ only in which verdict the target's match produces,
|
|
61
|
+
* so they are one traversal rather than two: `forbid` condemns an edge
|
|
62
|
+
* reaching what `to` claims, and `allow` condemns one reaching anything it
|
|
63
|
+
* does not.
|
|
64
|
+
*/
|
|
65
|
+
private evaluateAccessRule;
|
|
66
|
+
/** Reports every cycle an `acyclic` rule's selected nodes still form. */
|
|
67
|
+
private evaluateAcyclicRule;
|
|
68
|
+
/** Indexes a graph's nodes by identifier, so an edge can look its ends up. */
|
|
69
|
+
private indexNodes;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a rule judges this edge at all, before either end is selected.
|
|
72
|
+
*
|
|
73
|
+
* A rule naming no `edges` judges every one, which is the stricter reading
|
|
74
|
+
* and the right default for a rule about what a project may _depend on_.
|
|
75
|
+
* Naming `implicit: false` narrows it to what an
|
|
76
|
+
* `@nx/enforce-module-boundaries` `depConstraint` actually sees — that rule
|
|
77
|
+
* reads import statements, so an `implicitDependencies` entry is invisible
|
|
78
|
+
* to it. An edge at a level that draws no implicit edges is read as
|
|
79
|
+
* explicit, since a file import is not "explicitly" anything.
|
|
80
|
+
*/
|
|
81
|
+
private judgesEdge;
|
|
82
|
+
/**
|
|
83
|
+
* The node an edge names, or a bare one carrying only that identifier.
|
|
84
|
+
*
|
|
85
|
+
* An edge naming a node the graph does not list is judged on its identifier
|
|
86
|
+
* alone rather than skipped: silently dropping the edge would hide the rule
|
|
87
|
+
* rather than the mistake. The consequence is that a `path`, `project`, or
|
|
88
|
+
* `tags` selector cannot match such an endpoint, since a bare node carries
|
|
89
|
+
* none of them — so a `forbid` written against `path` reports nothing for
|
|
90
|
+
* it, which is the safer of the two wrong answers.
|
|
91
|
+
*
|
|
92
|
+
* Every real builder lists both ends of every edge it draws — an import
|
|
93
|
+
* graph only draws edges between files in its own `fileNames`, and the Nx
|
|
94
|
+
* and NestJS graphs both list every node they connect — so this is reached
|
|
95
|
+
* only by a hand-written graph.
|
|
96
|
+
*/
|
|
97
|
+
private resolveNode;
|
|
98
|
+
/**
|
|
99
|
+
* Every violation one graph's rules report, in the order the rules were
|
|
100
|
+
* declared.
|
|
101
|
+
*
|
|
102
|
+
* Declaration order rather than severity or node order: a configuration's
|
|
103
|
+
* author reads their own file top to bottom, and a report ordered the same
|
|
104
|
+
* way needs no key.
|
|
105
|
+
*/
|
|
106
|
+
evaluate(args: EvaluateBoundariesArguments): BoundaryViolation[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The order the four graph levels are judged in.
|
|
111
|
+
*
|
|
112
|
+
* Cheapest first, and it is not a small difference: the Nx level reads a graph
|
|
113
|
+
* the run already holds, while the NestJS level boots every container in
|
|
114
|
+
* preview mode and the TypeScript level builds a `ts.Program` per project. A
|
|
115
|
+
* workspace declaring only Nx rules never pays for either, because a level
|
|
116
|
+
* with no rules is not built at all.
|
|
117
|
+
*
|
|
118
|
+
* Written as a list rather than left implicit in the order of four `if`
|
|
119
|
+
* blocks, so the order a report comes out in is stated once, in the place a
|
|
120
|
+
* reader looks for it. `typescript` and `python` are `boundaries.fileImports`'s
|
|
121
|
+
* two nested languages, judged as separate levels here even though
|
|
122
|
+
* `codependix-file-imports` builds and exports them as one merged graph type.
|
|
123
|
+
*/
|
|
124
|
+
export declare const BOUNDARY_LEVEL_ORDER: readonly ["nxProjects", "nestjsModules", "typescript", "python"];
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Everything a boundary check reads about the workspace it is judging.
|
|
128
|
+
*
|
|
129
|
+
* Stated apart from `GraphRunContext` — the wider run context this package
|
|
130
|
+
* also owns, which carries an export mode the gate never reads — so a caller
|
|
131
|
+
* that only judges boundaries never has to name an export mode it has none
|
|
132
|
+
* of. `GraphRunContext` is structurally assignable to this, so nothing has to
|
|
133
|
+
* be repacked at the call site.
|
|
134
|
+
*/
|
|
135
|
+
export declare interface BoundaryCheckContext {
|
|
136
|
+
readonly configuration: ResolvedCodependixConfiguration;
|
|
137
|
+
/**
|
|
138
|
+
* The graph types this run judges.
|
|
139
|
+
*
|
|
140
|
+
* Read by `BoundaryCheckService.run` to skip every level under a disabled
|
|
141
|
+
* graph type before a single graph is built — see
|
|
142
|
+
* `RunContextService.resolveEnabledGraphTypes`, which is where a run
|
|
143
|
+
* resolves this from `--no-file-imports`, `--no-nestjs-modules`, and
|
|
144
|
+
* `--no-nx-projects`.
|
|
145
|
+
*/
|
|
146
|
+
readonly enabledGraphTypes: ReadonlySet<CodependixGraphType>;
|
|
147
|
+
readonly graph: NxProjectGraph;
|
|
148
|
+
/** Every project the graph knows, apart from the workspace root. */
|
|
149
|
+
readonly projects: NxProject[];
|
|
150
|
+
/**
|
|
151
|
+
* The projects the run was narrowed to, which is what every level judges.
|
|
152
|
+
*
|
|
153
|
+
* Identical to `projects` unless `--projects` or `--tags` named a
|
|
154
|
+
* selection, so the gate judges the whole workspace by default and a
|
|
155
|
+
* narrowed run is something the command line asked for explicitly.
|
|
156
|
+
*/
|
|
157
|
+
readonly selectedProjects: NxProject[];
|
|
158
|
+
readonly workingDirectory: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** One project whose graph could not be built, and why. */
|
|
162
|
+
export declare interface BoundaryCheckFailure {
|
|
163
|
+
readonly error: string;
|
|
164
|
+
readonly projectName: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Wires rule evaluation together with the four graph builders it judges. */
|
|
168
|
+
export declare class BoundaryCheckModule {
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* What one `--check boundaries` pass found.
|
|
173
|
+
*
|
|
174
|
+
* Failures are carried beside violations rather than thrown, for the same
|
|
175
|
+
* reason every export pass carries them: a NestJS project that cannot boot
|
|
176
|
+
* its container says nothing about whether the other thirty-six break a rule,
|
|
177
|
+
* and a run that abandoned the rest would report a smaller problem than it
|
|
178
|
+
* has.
|
|
179
|
+
*/
|
|
180
|
+
export declare interface BoundaryCheckOutcome {
|
|
181
|
+
readonly failures: BoundaryCheckFailure[];
|
|
182
|
+
readonly violations: BoundaryViolation[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Judges every level's graph against the rules declared for it.
|
|
187
|
+
*
|
|
188
|
+
* A level whose rule list is empty is never built at all, which is what makes
|
|
189
|
+
* the gate affordable: judging the NestJS level means booting every
|
|
190
|
+
* `framework:nestjs` container in preview mode, and judging the TypeScript
|
|
191
|
+
* level means building a `ts.Program` per project. A workspace declaring only
|
|
192
|
+
* Nx rules pays for neither.
|
|
193
|
+
*
|
|
194
|
+
* Nothing here writes or reads a destination. A violation goes to the console
|
|
195
|
+
* and the exit code, so `--check boundaries` leaves every committed export
|
|
196
|
+
* exactly as it found it — the property that lets it gate a branch, where the
|
|
197
|
+
* exports are expected to be behind the workspace they describe.
|
|
198
|
+
*/
|
|
199
|
+
export declare class BoundaryCheckService {
|
|
200
|
+
private readonly boundariesService;
|
|
201
|
+
private readonly boundaryGraphService;
|
|
202
|
+
private readonly moduleGraphService;
|
|
203
|
+
private readonly nestjsProjectService;
|
|
204
|
+
private readonly pythonService;
|
|
205
|
+
private readonly typescriptService;
|
|
206
|
+
private readonly workspaceGraphService;
|
|
207
|
+
constructor(boundariesService: BoundariesService, boundaryGraphService: BoundaryGraphService, moduleGraphService: ModuleGraphService, nestjsProjectService: NestjsProjectService, pythonService: PythonService, typescriptService: TypescriptService, workspaceGraphService: WorkspaceGraphService);
|
|
208
|
+
/** Turns a raised error into a `BoundaryCheckFailure` for the given project. */
|
|
209
|
+
private collectProjectFailure;
|
|
210
|
+
/**
|
|
211
|
+
* The `CodependixGraphType` each boundary level is judged under.
|
|
212
|
+
*
|
|
213
|
+
* Finer-grained than `CodependixGraphType` itself: `python` and
|
|
214
|
+
* `typescript` both fall under `fileImports`, since `codependix-file-imports`
|
|
215
|
+
* builds and exports both as one graph type even though `boundaries`
|
|
216
|
+
* still nests their rules by language. `--no-file-imports` therefore skips
|
|
217
|
+
* both levels together.
|
|
218
|
+
*/
|
|
219
|
+
private graphTypeForLevel;
|
|
220
|
+
/**
|
|
221
|
+
* Resolves one level's declared rules out of the nested boundaries shape.
|
|
222
|
+
*
|
|
223
|
+
* A record keyed by level rather than a switch, the same reason `runLevel`
|
|
224
|
+
* is one: every `CodependixBoundaryLevel` must have an entry, so a fifth
|
|
225
|
+
* level added to that union fails to compile here instead of silently
|
|
226
|
+
* resolving to nothing.
|
|
227
|
+
*/
|
|
228
|
+
private rulesForLevel;
|
|
229
|
+
/**
|
|
230
|
+
* Judges one level, whichever of the four builders it needs.
|
|
231
|
+
*
|
|
232
|
+
* A record keyed by level rather than a switch: the record type requires
|
|
233
|
+
* every `CodependixBoundaryLevel` to have an entry, so a fifth level added
|
|
234
|
+
* to that union fails to compile here instead of silently going unchecked.
|
|
235
|
+
*/
|
|
236
|
+
private runLevel;
|
|
237
|
+
/** Judges every `framework:nestjs` project's module graph. */
|
|
238
|
+
private runNestjsLevel;
|
|
239
|
+
/** Judges the whole-workspace Nx project graph. */
|
|
240
|
+
private runNxLevel;
|
|
241
|
+
/**
|
|
242
|
+
* Judges every project at one level, isolating each project's failure.
|
|
243
|
+
*
|
|
244
|
+
* The three per-project levels differ only in how a project is discovered
|
|
245
|
+
* and how its graph is built, so the loop around them is written once: a
|
|
246
|
+
* project that raises is collected as a failure and every other project is
|
|
247
|
+
* still judged. `buildGraph` may be asynchronous because the NestJS level's
|
|
248
|
+
* is — booting a container is the one graph this tool cannot build
|
|
249
|
+
* synchronously.
|
|
250
|
+
*/
|
|
251
|
+
private runProjectLevel;
|
|
252
|
+
/** Judges every `language:python` project's file-level import graph. */
|
|
253
|
+
private runPythonImportsLevel;
|
|
254
|
+
/** Judges every TypeScript project's file-level import graph. */
|
|
255
|
+
private runTypescriptImportsLevel;
|
|
256
|
+
/**
|
|
257
|
+
* Judges every level that has a rule to judge it by and whose graph type
|
|
258
|
+
* this run enabled.
|
|
259
|
+
*
|
|
260
|
+
* The four levels are independent, so a NestJS project failing to boot has
|
|
261
|
+
* no bearing on whether the Nx or import graphs break a rule, and every
|
|
262
|
+
* level is attempted regardless of what an earlier one reported. A level
|
|
263
|
+
* declaring no rule is skipped before anything is built, which is what
|
|
264
|
+
* keeps the gate affordable — and a level whose graph type
|
|
265
|
+
* `context.enabledGraphTypes` excludes is skipped the same way, so
|
|
266
|
+
* `--no-nestjs-modules` never boots a single container to judge it.
|
|
267
|
+
*/
|
|
268
|
+
run(context: BoundaryCheckContext): Promise<BoundaryCheckOutcome>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* One cycle found in a graph.
|
|
273
|
+
*
|
|
274
|
+
* `source` and `target` are the edge that closed it — the last node walked,
|
|
275
|
+
* and the node already on the path it returned to — so a cycle reports the
|
|
276
|
+
* same two endpoints every other violation does, without a caller having to
|
|
277
|
+
* index into `path` and prove to the compiler that it is non-empty.
|
|
278
|
+
*/
|
|
279
|
+
export declare interface BoundaryCycle {
|
|
280
|
+
/** The whole path, with the closing node repeated at the end. */
|
|
281
|
+
readonly path: readonly string[];
|
|
282
|
+
readonly source: string;
|
|
283
|
+
readonly target: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Finds the cycles in a graph, within a selected set of nodes.
|
|
288
|
+
*
|
|
289
|
+
* A cycle is the one finding no rule reading a single file can make, which is
|
|
290
|
+
* why it is a rule kind of its own rather than an access rule with a clever
|
|
291
|
+
* selector. Scoped to a node set rather than always the whole graph: a rule
|
|
292
|
+
* covering one directory should not fail because of a cycle running entirely
|
|
293
|
+
* through code it was never asked about.
|
|
294
|
+
*
|
|
295
|
+
* One cycle is reported per distinct set of nodes, keyed on the cycle rotated
|
|
296
|
+
* to start at its smallest node — so a three-node cycle is one finding rather
|
|
297
|
+
* than the same finding printed once per node it passes through.
|
|
298
|
+
*/
|
|
299
|
+
export declare class BoundaryCyclesService {
|
|
300
|
+
constructor();
|
|
301
|
+
/** Builds the outgoing-edge map, keeping only edges inside the node set. */
|
|
302
|
+
private buildAdjacency;
|
|
303
|
+
/**
|
|
304
|
+
* The key every cycle over the same nodes shares.
|
|
305
|
+
*
|
|
306
|
+
* The node set rather than the path: `a → b → a` and `b → a → b` are one
|
|
307
|
+
* finding written two ways, and so are the two directions round a
|
|
308
|
+
* three-node cycle. Reporting the first path found through a set of nodes
|
|
309
|
+
* and nothing else is what keeps one tangle from printing as six findings.
|
|
310
|
+
*/
|
|
311
|
+
private buildCycleKey;
|
|
312
|
+
/** Records the cycle closed by walking back to a node already on the path. */
|
|
313
|
+
private recordCycle;
|
|
314
|
+
/** Walks one node's subtree depth-first, recording every cycle it closes. */
|
|
315
|
+
private walk;
|
|
316
|
+
/**
|
|
317
|
+
* Every cycle lying entirely inside the given node set.
|
|
318
|
+
*
|
|
319
|
+
* Nodes are walked in sorted order and each cycle is reported starting from
|
|
320
|
+
* the node the walk reached first, so the same graph always produces the
|
|
321
|
+
* same findings in the same order — a report that reshuffles itself between
|
|
322
|
+
* runs is one nobody can diff.
|
|
323
|
+
*/
|
|
324
|
+
findCycles(args: FindCyclesArguments): BoundaryCycle[];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* One node depending on another, at whatever level the graph is.
|
|
329
|
+
*
|
|
330
|
+
* `implicit` is carried rather than dropped because it is the one fact a lint
|
|
331
|
+
* rule reading import statements cannot reach: an Nx `implicitDependencies`
|
|
332
|
+
* entry creates a project-graph edge with no import statement to flag. Absent
|
|
333
|
+
* at every level that has no such notion, rather than defaulted to `false` —
|
|
334
|
+
* a file import is not "explicitly" anything, it is just an import.
|
|
335
|
+
*/
|
|
336
|
+
export declare interface BoundaryEdge {
|
|
337
|
+
readonly implicit?: boolean | undefined;
|
|
338
|
+
readonly source: string;
|
|
339
|
+
readonly target: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* One built graph, reduced to what rule evaluation needs.
|
|
344
|
+
*
|
|
345
|
+
* Deliberately not `Neighborhood`, `NestjsModuleGraph`, `TypescriptImportGraph`
|
|
346
|
+
* or `PythonImportGraph`: reading any of those would drag `@nx/devkit`,
|
|
347
|
+
* `@nestjs/core` and `typescript` behind anything that wants only rule
|
|
348
|
+
* evaluation. The four already share an identical `{ source, target }` edge
|
|
349
|
+
* shape by construction, so the adapters that flatten them into this live in
|
|
350
|
+
* the host that already builds all four — see `codependix-cli`.
|
|
351
|
+
*/
|
|
352
|
+
export declare interface BoundaryGraph {
|
|
353
|
+
readonly edges: readonly BoundaryEdge[];
|
|
354
|
+
/** Which of codependix's four levels this graph was built at. */
|
|
355
|
+
readonly level: CodependixBoundaryLevel;
|
|
356
|
+
readonly nodes: readonly BoundaryNode[];
|
|
357
|
+
/**
|
|
358
|
+
* What the graph covers: an Nx project name, or the workspace itself.
|
|
359
|
+
*
|
|
360
|
+
* Reported beside every violation, because the same rule evaluated at file
|
|
361
|
+
* level fails once per project and a bare pair of file paths does not say
|
|
362
|
+
* which project's files they are.
|
|
363
|
+
*/
|
|
364
|
+
readonly scope: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Flattens each of codependix's four graphs into the one shape rules read.
|
|
369
|
+
*
|
|
370
|
+
* The adapters live here, in the host that already builds all four, rather
|
|
371
|
+
* than in `@codependix/boundaries` — which would otherwise have to depend on
|
|
372
|
+
* `@nestjs/core` and `typescript` to name the types it translates, for a
|
|
373
|
+
* package whose whole job is evaluating rules. The four
|
|
374
|
+
* already share an identical `{ source, target }` edge shape by construction,
|
|
375
|
+
* so each adapter is only ever nodes, edges, and the attributes rules select
|
|
376
|
+
* on.
|
|
377
|
+
*
|
|
378
|
+
* What each level knows differs, and the node shape says so: an Nx project
|
|
379
|
+
* carries tags and a root, a file carries its project-relative path and the
|
|
380
|
+
* project it belongs to, and a NestJS module carries its class name, declaring
|
|
381
|
+
* file path, and project name.
|
|
382
|
+
*/
|
|
383
|
+
export declare class BoundaryGraphService {
|
|
384
|
+
constructor();
|
|
385
|
+
/** Builds one file-level graph's nodes, which both languages share. */
|
|
386
|
+
private buildFileNodes;
|
|
387
|
+
/**
|
|
388
|
+
* A project's workspace-relative root, or nothing when the graph knows of a
|
|
389
|
+
* project the discovered list does not carry.
|
|
390
|
+
*/
|
|
391
|
+
private resolveProjectRoot;
|
|
392
|
+
/**
|
|
393
|
+
* Adapts one project's NestJS module graph.
|
|
394
|
+
*
|
|
395
|
+
* Nodes carry a name, their project-relative declaring file path, and the
|
|
396
|
+
* project name they belong to.
|
|
397
|
+
*/
|
|
398
|
+
buildNestjsGraph(graph: NestjsModuleGraph): BoundaryGraph;
|
|
399
|
+
/**
|
|
400
|
+
* Adapts the whole-workspace Nx project graph.
|
|
401
|
+
*
|
|
402
|
+
* The workspace graph rather than each project's one-hop neighborhood: a
|
|
403
|
+
* rule is a statement about the shape of the graph, and a neighborhood is
|
|
404
|
+
* the same graph shown twice from either end of every edge.
|
|
405
|
+
*
|
|
406
|
+
* Tags come off the `NxProject`s themselves, because they are what makes
|
|
407
|
+
* this level worth having: a tag rule reaches nothing unless the nodes it
|
|
408
|
+
* judges carry them.
|
|
409
|
+
*/
|
|
410
|
+
buildNxGraph(args: {
|
|
411
|
+
projects: readonly NxProject[];
|
|
412
|
+
scope: string;
|
|
413
|
+
workingDirectory: string;
|
|
414
|
+
workspaceGraph: WorkspaceGraph;
|
|
415
|
+
}): BoundaryGraph;
|
|
416
|
+
/** Adapts one project's Python file-level import graph. */
|
|
417
|
+
buildPythonImportGraph(graph: PythonImportGraph): BoundaryGraph;
|
|
418
|
+
/** Adapts one project's TypeScript file-level import graph. */
|
|
419
|
+
buildTypescriptImportGraph(graph: TypescriptImportGraph): BoundaryGraph;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* One node in a graph, with whatever a level knows about it.
|
|
424
|
+
*
|
|
425
|
+
* One shape for three vocabularies: an Nx project has a name, a root, and
|
|
426
|
+
* tags; a file has a project-relative path and project; a NestJS module has a
|
|
427
|
+
* class name, declaring file path, and project. Every field beyond `id` is
|
|
428
|
+
* therefore optional, and a selector naming one a level does not carry
|
|
429
|
+
* matches nothing there rather than everything — see `BoundarySelectorService`.
|
|
430
|
+
*
|
|
431
|
+
* No `kind` field: the graph already states its `level`, and a second field
|
|
432
|
+
* saying the same thing is a second thing that can be wrong.
|
|
433
|
+
*/
|
|
434
|
+
export declare interface BoundaryNode {
|
|
435
|
+
readonly id: string;
|
|
436
|
+
readonly path?: string | undefined;
|
|
437
|
+
readonly project?: string | undefined;
|
|
438
|
+
readonly tags?: readonly string[] | undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Renders violations into the lines a run prints.
|
|
443
|
+
*
|
|
444
|
+
* Rendering lives here rather than in the host for the same reason each graph
|
|
445
|
+
* package renders its own mermaid: the package that knows what a finding means
|
|
446
|
+
* is the one that should decide how it reads. There is no configured
|
|
447
|
+
* destination and no file — a violation report is a list of things currently
|
|
448
|
+
* wrong, which is not a document worth regenerating on the default branch or
|
|
449
|
+
* checking for staleness.
|
|
450
|
+
*/
|
|
451
|
+
export declare class BoundaryReportService {
|
|
452
|
+
constructor();
|
|
453
|
+
/**
|
|
454
|
+
* One line summarizing what a run found.
|
|
455
|
+
*
|
|
456
|
+
* Counts rules as well as violations, because the two answer different
|
|
457
|
+
* questions: one broken rule reporting forty edges is a single decision to
|
|
458
|
+
* revisit, and forty rules reporting one edge each is not.
|
|
459
|
+
*/
|
|
460
|
+
renderSummary(violations: readonly BoundaryViolation[]): string;
|
|
461
|
+
/**
|
|
462
|
+
* One line per violation, each naming its level and scope before the rule's
|
|
463
|
+
* own sentence.
|
|
464
|
+
*
|
|
465
|
+
* The level and scope lead because the message cannot carry them: the same
|
|
466
|
+
* rule evaluated at file level fails once per project, and a bare pair of
|
|
467
|
+
* file paths does not say whose files they are.
|
|
468
|
+
*/
|
|
469
|
+
renderViolations(violations: readonly BoundaryViolation[]): string[];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Decides which nodes a rule's selector claims.
|
|
474
|
+
*
|
|
475
|
+
* Split out from `BoundariesService` because it is the one part of rule
|
|
476
|
+
* evaluation with no opinion about rules at all: a selector and a node go in,
|
|
477
|
+
* and a yes or no comes out. Both rule kinds ask it the same question, and so
|
|
478
|
+
* does anything that later wants to know what a rule covers without running
|
|
479
|
+
* it.
|
|
480
|
+
*/
|
|
481
|
+
export declare class BoundarySelectorService {
|
|
482
|
+
constructor();
|
|
483
|
+
/**
|
|
484
|
+
* Whether one of a node's string attributes matches a list of globs.
|
|
485
|
+
*
|
|
486
|
+
* A selector naming an attribute the node's level does not carry matches
|
|
487
|
+
* nothing rather than everything — a `path` rule evaluated against a NestJS
|
|
488
|
+
* module graph, which carries no file paths, selects no module instead of
|
|
489
|
+
* silently selecting all of them.
|
|
490
|
+
*/
|
|
491
|
+
private matchesGlobs;
|
|
492
|
+
/**
|
|
493
|
+
* Whether a node carries a tag matching one of a list of globs.
|
|
494
|
+
*
|
|
495
|
+
* One tag matching is enough, which is what makes `tags: ["type:package"]`
|
|
496
|
+
* read the way an Nx tag list does — a project carries several, and a rule
|
|
497
|
+
* naming one is asking whether that one is among them.
|
|
498
|
+
*/
|
|
499
|
+
private matchesTags;
|
|
500
|
+
/**
|
|
501
|
+
* Whether a selector claims a node.
|
|
502
|
+
*
|
|
503
|
+
* Every field the selector states must match — the fields narrow each
|
|
504
|
+
* other, so `{ project: ["lexico"], path: ["src/**"] }` means a file in
|
|
505
|
+
* lexico's `src` rather than either of those. Within one field, one glob
|
|
506
|
+
* matching is enough.
|
|
507
|
+
*/
|
|
508
|
+
matches(node: BoundaryNode, selector: CodependixBoundarySelector): boolean;
|
|
509
|
+
/**
|
|
510
|
+
* The ids of every node a selector claims.
|
|
511
|
+
*
|
|
512
|
+
* A selector of `undefined` claims every node, which is what an `acyclic`
|
|
513
|
+
* rule naming no scope means. An empty selector is a different thing and
|
|
514
|
+
* never reaches here: the configuration schema refuses one outright, since
|
|
515
|
+
* a selector stating nothing reads exactly like a typo.
|
|
516
|
+
*/
|
|
517
|
+
selectIds(nodes: readonly BoundaryNode[], selector: CodependixBoundarySelector | undefined): Set<string>;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** One edge, or one cycle, breaking one declared rule. */
|
|
521
|
+
export declare interface BoundaryViolation {
|
|
522
|
+
/**
|
|
523
|
+
* The whole cycle, closing node repeated at the end, for an `acyclic` rule.
|
|
524
|
+
*
|
|
525
|
+
* Absent for an access rule, which is about one edge and has no path to
|
|
526
|
+
* report.
|
|
527
|
+
*/
|
|
528
|
+
readonly cycle: readonly string[] | undefined;
|
|
529
|
+
readonly level: CodependixBoundaryLevel;
|
|
530
|
+
/** The sentence reported, whether the rule's own or the generated one. */
|
|
531
|
+
readonly message: string;
|
|
532
|
+
/** The `name` of the rule that reported it. */
|
|
533
|
+
readonly rule: string;
|
|
534
|
+
readonly scope: string;
|
|
535
|
+
readonly source: string;
|
|
536
|
+
readonly target: string;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* The level a boundary graph was built at, or a violation was found at.
|
|
541
|
+
*
|
|
542
|
+
* Finer-grained than `CodependixGraphType`: `fileImports` builds one merged
|
|
543
|
+
* graph type per project, but `boundaries.fileImports` still nests rules by
|
|
544
|
+
* language, and a violation has to say which language's graph it came from.
|
|
545
|
+
* `nestjsModules` and `nxProjects` match `CodependixGraphType` one for one,
|
|
546
|
+
* since those two levels carry no such split.
|
|
547
|
+
*/
|
|
548
|
+
declare type CodependixBoundaryLevel = "nestjsModules" | "nxProjects" | "python" | "typescript";
|
|
549
|
+
|
|
550
|
+
/** How a cycle's nodes are joined when one is reported. */
|
|
551
|
+
export declare const CYCLE_SEPARATOR = " \u2192 ";
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* The sentence an `acyclic` rule reports when it names none of its own.
|
|
555
|
+
*
|
|
556
|
+
* Names the whole path rather than only the edge that closed it: a cycle is a
|
|
557
|
+
* statement about a shape, and the two nodes of its closing edge are the
|
|
558
|
+
* least useful two to be handed.
|
|
559
|
+
*/
|
|
560
|
+
export declare const describeCycle: (args: {
|
|
561
|
+
cycle: readonly string[];
|
|
562
|
+
rule: string;
|
|
563
|
+
}) => string;
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The sentence a violated `allow` rule reports when it names none of its own.
|
|
567
|
+
*
|
|
568
|
+
* Worded as a permitted surface rather than as a forbidden edge, because that
|
|
569
|
+
* is what distinguishes it from `forbid`: the reader's next question is what
|
|
570
|
+
* the rule does allow, and the rule's name is the only place this can point
|
|
571
|
+
* them at without restating the configuration.
|
|
572
|
+
*/
|
|
573
|
+
export declare const describeDisallowedEdge: (args: {
|
|
574
|
+
rule: string;
|
|
575
|
+
source: string;
|
|
576
|
+
target: string;
|
|
577
|
+
}) => string;
|
|
578
|
+
|
|
579
|
+
/** The sentence a violated `forbid` rule reports when it names none of its own. */
|
|
580
|
+
export declare const describeForbiddenEdge: (args: {
|
|
581
|
+
rule: string;
|
|
582
|
+
source: string;
|
|
583
|
+
target: string;
|
|
584
|
+
}) => string;
|
|
585
|
+
|
|
586
|
+
/** Arguments accepted when judging one graph against one level's rules. */
|
|
587
|
+
export declare interface EvaluateBoundariesArguments {
|
|
588
|
+
readonly graph: BoundaryGraph;
|
|
589
|
+
readonly rules: readonly CodependixBoundaryRule[];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Arguments accepted when finding the cycles in a graph. */
|
|
593
|
+
export declare interface FindCyclesArguments {
|
|
594
|
+
readonly edges: readonly BoundaryEdge[];
|
|
595
|
+
/** The nodes a cycle must lie entirely within. */
|
|
596
|
+
readonly nodeIds: ReadonlySet<string>;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Everything every graph-type pass reads, resolved once per run rather than
|
|
601
|
+
* once per pass — see `GraphRunService.run`.
|
|
602
|
+
*
|
|
603
|
+
* Stated here, beside `BoundaryCheckContext`, because resolving it is the
|
|
604
|
+
* first thing every run does and the boundary gate is one of its readers:
|
|
605
|
+
* the wider context carries an export mode the gate never looks at, and is
|
|
606
|
+
* structurally assignable to `BoundaryCheckContext`, so nothing is repacked
|
|
607
|
+
* at the call site.
|
|
608
|
+
*/
|
|
609
|
+
export declare interface GraphRunContext {
|
|
610
|
+
configuration: ResolvedCodependixConfiguration;
|
|
611
|
+
/**
|
|
612
|
+
* The graph types this run builds, checks, and writes.
|
|
613
|
+
*
|
|
614
|
+
* All three unless `--no-file-imports`, `--no-nestjs-modules`, or
|
|
615
|
+
* `--no-nx-projects` narrowed it — see `RunContextService.build`. Read by
|
|
616
|
+
* `GraphRunService.run` to skip a whole pass, and by
|
|
617
|
+
* `BoundaryCheckService.run` to skip a boundary level, so a developer can
|
|
618
|
+
* run a narrower, faster check locally without editing configuration.
|
|
619
|
+
*/
|
|
620
|
+
enabledGraphTypes: ReadonlySet<CodependixGraphType>;
|
|
621
|
+
graph: NxProjectGraph;
|
|
622
|
+
mode: CodependixRunMode;
|
|
623
|
+
/**
|
|
624
|
+
* Every project's own `codependix.config.ts`, keyed by project name — or
|
|
625
|
+
* `undefined` for a project naming none of its own.
|
|
626
|
+
*
|
|
627
|
+
* Loaded once per run, alongside `projects`, so every pass reads the same
|
|
628
|
+
* snapshot rather than each re-reading the filesystem for every graph type
|
|
629
|
+
* it resolves. Read as-is by `ConfigurationService.resolveForProject` — see
|
|
630
|
+
* that method for why no further merge happens here.
|
|
631
|
+
*/
|
|
632
|
+
projectConfigurations: Map<string, CodependixProjectConfiguration | undefined>;
|
|
633
|
+
/** Every project the graph knows, apart from the workspace root. */
|
|
634
|
+
projects: NxProject[];
|
|
635
|
+
/**
|
|
636
|
+
* The projects `--projects` and `--tags` narrowed the run to.
|
|
637
|
+
*
|
|
638
|
+
* Identical to `projects` when a run named neither, which is what keeps the
|
|
639
|
+
* Workspace Graph whole and the boundary gate judging every project by
|
|
640
|
+
* default. `include`/`exclude` never reach this: they decide which projects
|
|
641
|
+
* have exports written, not which projects a graph is drawn over.
|
|
642
|
+
*/
|
|
643
|
+
selectedProjects: NxProject[];
|
|
644
|
+
workingDirectory: string;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** Arguments accepted when judging one graph level against its rules. */
|
|
648
|
+
export declare interface LevelCheckArguments {
|
|
649
|
+
readonly context: BoundaryCheckContext;
|
|
650
|
+
readonly level: CodependixBoundaryLevel;
|
|
651
|
+
readonly rules: readonly CodependixBoundaryRule[];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Wires run-context resolution to the configuration and project graph it
|
|
656
|
+
* reads, which are the only two things a run has to resolve before any pass
|
|
657
|
+
* can run.
|
|
658
|
+
*/
|
|
659
|
+
export declare class RunContextModule {
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Resolves everything one run reads, once, before any pass runs.
|
|
664
|
+
*
|
|
665
|
+
* Kept apart from the passes that read it because the two answer different
|
|
666
|
+
* questions: what this workspace is, versus what to do about it. Every pass —
|
|
667
|
+
* the four export passes, the Workspace Graph, and the boundary gate — is
|
|
668
|
+
* handed the same resolved context rather than resolving its own, so a run
|
|
669
|
+
* reads the project graph once and judges one workspace.
|
|
670
|
+
*/
|
|
671
|
+
export declare class RunContextService {
|
|
672
|
+
private readonly configurationService;
|
|
673
|
+
private readonly neighborhoodService;
|
|
674
|
+
constructor(configurationService: ConfigurationService, neighborhoodService: NeighborhoodService);
|
|
675
|
+
/**
|
|
676
|
+
* Loads every project's own `codependix.config.ts`, keyed by project name.
|
|
677
|
+
*
|
|
678
|
+
* Loaded once per run, up front, rather than once per graph-type pass: a
|
|
679
|
+
* project with no file of its own is a normal, expected outcome rather than
|
|
680
|
+
* a failure, so every project is attempted and the map simply holds
|
|
681
|
+
* `undefined` for the ones naming none.
|
|
682
|
+
*/
|
|
683
|
+
private loadProjectConfigurations;
|
|
684
|
+
/**
|
|
685
|
+
* Reads the three graph-type toggle flags into the set of graph types this
|
|
686
|
+
* run builds, checks, and writes.
|
|
687
|
+
*
|
|
688
|
+
* A graph type is enabled unless its own `--no-*` flag disabled it —
|
|
689
|
+
* `--file-imports`/`--nestjs-modules`/`--nx-projects` exist only for
|
|
690
|
+
* symmetry with the negated form, matching every graph type's default of
|
|
691
|
+
* "on" when neither flag was given.
|
|
692
|
+
*/
|
|
693
|
+
private resolveEnabledGraphTypes;
|
|
694
|
+
/**
|
|
695
|
+
* Resolves a supplied project graph's path against the workspace root.
|
|
696
|
+
*
|
|
697
|
+
* The same root every export path resolves against — `--directory` keeps
|
|
698
|
+
* its one meaning, and a supplied graph's workspace-relative node roots
|
|
699
|
+
* resolve underneath it too.
|
|
700
|
+
*/
|
|
701
|
+
private resolveProjectGraphPath;
|
|
702
|
+
/**
|
|
703
|
+
* Narrows every project to the set `--projects` and `--tags` named.
|
|
704
|
+
*
|
|
705
|
+
* A run naming neither selects every project, which is what keeps the
|
|
706
|
+
* Workspace Graph whole and the boundary gate judging the whole workspace by
|
|
707
|
+
* default. `include`/`exclude` deliberately do not reach this: they decide
|
|
708
|
+
* which projects have exports written for them, not which projects a graph
|
|
709
|
+
* is drawn over.
|
|
710
|
+
*/
|
|
711
|
+
private selectProjects;
|
|
712
|
+
/**
|
|
713
|
+
* Reads the configuration and the project graph a run is about to act on.
|
|
714
|
+
*
|
|
715
|
+
* Called once per run, by the command — every pass takes the context it
|
|
716
|
+
* returns. `--check boundaries` reads exactly the same one, which is what
|
|
717
|
+
* lets a single run both export and gate without reading the workspace
|
|
718
|
+
* twice.
|
|
719
|
+
*/
|
|
720
|
+
build(args: {
|
|
721
|
+
mode: CodependixRunMode;
|
|
722
|
+
options: MapCommandOptions;
|
|
723
|
+
workingDirectory: string;
|
|
724
|
+
}): Promise<GraphRunContext>;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* The scope a violation found in the whole-workspace Nx graph is reported
|
|
729
|
+
* under.
|
|
730
|
+
*
|
|
731
|
+
* The Nx level is judged once for the repository rather than once per
|
|
732
|
+
* project, so it has no project name to report against.
|
|
733
|
+
*/
|
|
734
|
+
export declare const WORKSPACE_SCOPE = "workspace";
|
|
735
|
+
|
|
736
|
+
export { }
|