@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.
@@ -0,0 +1,509 @@
1
+ import { Injectable as e, Module as t } from "@nestjs/common";
2
+ import n from "node:path";
3
+ import { PythonModule as r, PythonService as i, TypescriptModule as a, TypescriptService as o } from "@codependix/file-imports";
4
+ import { ModuleGraphModule as s, ModuleGraphService as c, NestjsProjectModule as l, NestjsProjectService as u } from "@codependix/nestjs-modules";
5
+ import { NeighborhoodModule as d, NeighborhoodService as f, WorkspaceGraphModule as p, WorkspaceGraphService as m } from "@codependix/nx-projects";
6
+ import { CODEPENDIX_GRAPH_TYPES as h, ConfigurationModule as g, ConfigurationService as _ } from "@codependix/configuration";
7
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundaries.constants.ts
8
+ var v = " → ", y = (e) => `${e.rule}: ${e.cycle.join(v)} is a cycle.`, b = (e) => `${e.rule}: ${e.source} may not depend on ${e.target}, which the rule's allowed targets do not cover.`, x = (e) => `${e.rule}: ${e.source} must not depend on ${e.target}.`;
9
+ //#endregion
10
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateMetadata.js
11
+ function S(e, t) {
12
+ if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
13
+ }
14
+ //#endregion
15
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorate.js
16
+ function C(e, t, n, r) {
17
+ var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
18
+ if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
19
+ else for (var s = e.length - 1; s >= 0; s--) (o = e[s]) && (a = (i < 3 ? o(a) : i > 3 ? o(t, n, a) : o(t, n)) || a);
20
+ return i > 3 && a && Object.defineProperty(t, n, a), a;
21
+ }
22
+ //#endregion
23
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundary-cycles.service.ts
24
+ var w = class {
25
+ constructor() {}
26
+ buildAdjacency(e) {
27
+ let t = /* @__PURE__ */ new Map();
28
+ for (let n of e.edges) {
29
+ if (!e.nodeIds.has(n.source) || !e.nodeIds.has(n.target)) continue;
30
+ let r = t.get(n.source) ?? [];
31
+ r.push(n.target), t.set(n.source, r);
32
+ }
33
+ for (let [e, n] of t) t.set(e, n.toSorted());
34
+ return t;
35
+ }
36
+ buildCycleKey(e) {
37
+ return [...new Set(e)].toSorted().join(">");
38
+ }
39
+ recordCycle(e) {
40
+ let { source: t, state: n, target: r } = e, i = [...n.stack.slice(n.stack.indexOf(r)), r], a = this.buildCycleKey(i);
41
+ n.keys.has(a) || (n.keys.add(a), n.cycles.push({
42
+ path: i,
43
+ source: t,
44
+ target: r
45
+ }));
46
+ }
47
+ walk(e) {
48
+ let { adjacency: t, node: n, state: r } = e;
49
+ r.onStack.add(n), r.stack.push(n);
50
+ for (let e of t.get(n) ?? []) {
51
+ if (r.onStack.has(e)) {
52
+ this.recordCycle({
53
+ source: n,
54
+ state: r,
55
+ target: e
56
+ });
57
+ continue;
58
+ }
59
+ r.visited.has(e) || this.walk({
60
+ adjacency: t,
61
+ node: e,
62
+ state: r
63
+ });
64
+ }
65
+ r.stack.pop(), r.onStack.delete(n), r.visited.add(n);
66
+ }
67
+ findCycles(e) {
68
+ let t = this.buildAdjacency(e), n = {
69
+ cycles: [],
70
+ keys: /* @__PURE__ */ new Set(),
71
+ onStack: /* @__PURE__ */ new Set(),
72
+ stack: [],
73
+ visited: /* @__PURE__ */ new Set()
74
+ };
75
+ for (let r of [...e.nodeIds].toSorted()) n.visited.has(r) || this.walk({
76
+ adjacency: t,
77
+ node: r,
78
+ state: n
79
+ });
80
+ return n.cycles;
81
+ }
82
+ };
83
+ w = C([e(), S("design:paramtypes", [])], w);
84
+ //#endregion
85
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundary-selector.service.ts
86
+ var T = class {
87
+ constructor() {}
88
+ matchesGlobs(e, t) {
89
+ return t === void 0 || e !== void 0 && t.some((t) => n.matchesGlob(e, t));
90
+ }
91
+ matchesTags(e, t) {
92
+ return t === void 0 || e !== void 0 && t.some((t) => e.some((e) => n.matchesGlob(e, t)));
93
+ }
94
+ matches(e, t) {
95
+ return this.matchesGlobs(e.id, t.id) && this.matchesGlobs(e.path, t.path) && this.matchesGlobs(e.project, t.project) && this.matchesTags(e.tags, t.tags);
96
+ }
97
+ selectIds(e, t) {
98
+ return t === void 0 ? new Set(e.map((e) => e.id)) : new Set(e.filter((e) => this.matches(e, t)).map((e) => e.id));
99
+ }
100
+ };
101
+ T = C([e(), S("design:paramtypes", [])], T);
102
+ //#endregion
103
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundaries.service.ts
104
+ var E = class {
105
+ cyclesService;
106
+ selectorService;
107
+ constructor(e, t) {
108
+ this.cyclesService = e, this.selectorService = t;
109
+ }
110
+ buildAccessViolation(e) {
111
+ let { edge: t, graph: n, rule: r } = e, i = r.kind === "forbid" ? x : b;
112
+ return {
113
+ cycle: void 0,
114
+ level: n.level,
115
+ message: this.buildMessage({
116
+ generated: i({
117
+ rule: r.name,
118
+ source: t.source,
119
+ target: t.target
120
+ }),
121
+ message: r.message
122
+ }),
123
+ rule: r.name,
124
+ scope: n.scope,
125
+ source: t.source,
126
+ target: t.target
127
+ };
128
+ }
129
+ buildMessage(e) {
130
+ return e.message === void 0 ? e.generated : `${e.generated} ${e.message}`;
131
+ }
132
+ evaluateAccessRule(e) {
133
+ let { graph: t, rule: n } = e, r = this.indexNodes(t), i = [];
134
+ for (let e of t.edges) {
135
+ if (!this.judgesEdge({
136
+ edge: e,
137
+ rule: n
138
+ })) continue;
139
+ let a = this.resolveNode(r, e.source);
140
+ if (!this.selectorService.matches(a, n.from)) continue;
141
+ let o = this.resolveNode(r, e.target);
142
+ this.selectorService.matches(o, n.to) === (n.kind === "forbid") && i.push(this.buildAccessViolation({
143
+ edge: e,
144
+ graph: t,
145
+ rule: n
146
+ }));
147
+ }
148
+ return i;
149
+ }
150
+ evaluateAcyclicRule(e) {
151
+ let { graph: t, rule: n } = e, r = this.selectorService.selectIds(t.nodes, n.nodes);
152
+ return this.cyclesService.findCycles({
153
+ edges: t.edges,
154
+ nodeIds: r
155
+ }).map((e) => ({
156
+ cycle: e.path,
157
+ level: t.level,
158
+ message: this.buildMessage({
159
+ generated: y({
160
+ cycle: e.path,
161
+ rule: n.name
162
+ }),
163
+ message: n.message
164
+ }),
165
+ rule: n.name,
166
+ scope: t.scope,
167
+ source: e.source,
168
+ target: e.target
169
+ }));
170
+ }
171
+ indexNodes(e) {
172
+ return new Map(e.nodes.map((e) => [e.id, e]));
173
+ }
174
+ judgesEdge(e) {
175
+ let t = e.rule.edges?.implicit;
176
+ return t === void 0 || (e.edge.implicit ?? !1) === t;
177
+ }
178
+ resolveNode(e, t) {
179
+ return e.get(t) ?? { id: t };
180
+ }
181
+ evaluate(e) {
182
+ return e.rules.flatMap((t) => t.kind === "acyclic" ? this.evaluateAcyclicRule({
183
+ graph: e.graph,
184
+ rule: t
185
+ }) : this.evaluateAccessRule({
186
+ graph: e.graph,
187
+ rule: t
188
+ }));
189
+ }
190
+ };
191
+ E = C([e(), S("design:paramtypes", [w === void 0 ? Object : w, T === void 0 ? Object : T])], E);
192
+ //#endregion
193
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundary-report.service.ts
194
+ var D = class {
195
+ constructor() {}
196
+ renderSummary(e) {
197
+ if (e.length === 0) return "No boundary violations.";
198
+ let t = new Set(e.map((e) => e.rule)), n = e.length === 1 ? "violation" : "violations", r = t.size === 1 ? "rule" : "rules";
199
+ return `${e.length} boundary ${n} across ${t.size} ${r}.`;
200
+ }
201
+ renderViolations(e) {
202
+ return e.map((e) => `${e.level} ${e.scope}: ${e.message}`);
203
+ }
204
+ };
205
+ D = C([e(), S("design:paramtypes", [])], D);
206
+ //#endregion
207
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundaries/boundaries.module.ts
208
+ var O = class {};
209
+ O = C([t({
210
+ controllers: [],
211
+ exports: [
212
+ E,
213
+ w,
214
+ D,
215
+ T
216
+ ],
217
+ imports: [],
218
+ providers: [
219
+ E,
220
+ w,
221
+ D,
222
+ T
223
+ ]
224
+ })], O);
225
+ //#endregion
226
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundary-check/boundary-check.constants.ts
227
+ var k = "workspace", A = [
228
+ "nxProjects",
229
+ "nestjsModules",
230
+ "typescript",
231
+ "python"
232
+ ], j = class {
233
+ constructor() {}
234
+ buildFileNodes(e) {
235
+ return e.fileNames.map((t) => ({
236
+ id: t,
237
+ path: t,
238
+ project: e.projectName
239
+ }));
240
+ }
241
+ resolveProjectRoot(e, t) {
242
+ return e === void 0 ? void 0 : n.relative(t, e);
243
+ }
244
+ buildNestjsGraph(e) {
245
+ return {
246
+ edges: e.edges,
247
+ level: "nestjsModules",
248
+ nodes: e.nodes.map((t) => ({
249
+ id: t.name,
250
+ path: t.declaringFile,
251
+ project: e.projectName
252
+ })),
253
+ scope: e.projectName
254
+ };
255
+ }
256
+ buildNxGraph(e) {
257
+ let t = new Map(e.projects.map((e) => [e.name, e]));
258
+ return {
259
+ edges: e.workspaceGraph.edges,
260
+ level: "nxProjects",
261
+ nodes: e.workspaceGraph.projectNames.map((n) => ({
262
+ id: n,
263
+ path: this.resolveProjectRoot(t.get(n)?.absoluteRoot, e.workingDirectory),
264
+ project: n,
265
+ tags: t.get(n)?.tags
266
+ })),
267
+ scope: e.scope
268
+ };
269
+ }
270
+ buildPythonImportGraph(e) {
271
+ return {
272
+ edges: e.edges,
273
+ level: "python",
274
+ nodes: this.buildFileNodes({
275
+ fileNames: e.fileNames,
276
+ projectName: e.projectName
277
+ }),
278
+ scope: e.projectName
279
+ };
280
+ }
281
+ buildTypescriptImportGraph(e) {
282
+ return {
283
+ edges: e.edges,
284
+ level: "typescript",
285
+ nodes: this.buildFileNodes({
286
+ fileNames: e.fileNames,
287
+ projectName: e.projectName
288
+ }),
289
+ scope: e.projectName
290
+ };
291
+ }
292
+ };
293
+ j = C([e(), S("design:paramtypes", [])], j);
294
+ //#endregion
295
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundary-check/boundary-check.service.ts
296
+ var M = class {
297
+ boundariesService;
298
+ boundaryGraphService;
299
+ moduleGraphService;
300
+ nestjsProjectService;
301
+ pythonService;
302
+ typescriptService;
303
+ workspaceGraphService;
304
+ constructor(e, t, n, r, i, a, o) {
305
+ this.boundariesService = e, this.boundaryGraphService = t, this.moduleGraphService = n, this.nestjsProjectService = r, this.pythonService = i, this.typescriptService = a, this.workspaceGraphService = o;
306
+ }
307
+ collectProjectFailure(e, t) {
308
+ return {
309
+ error: t instanceof Error ? t.message : String(t),
310
+ projectName: e
311
+ };
312
+ }
313
+ graphTypeForLevel(e) {
314
+ return {
315
+ nestjsModules: "nestjsModules",
316
+ nxProjects: "nxProjects",
317
+ python: "fileImports",
318
+ typescript: "fileImports"
319
+ }[e];
320
+ }
321
+ rulesForLevel(e, t) {
322
+ return {
323
+ nestjsModules: e.nestjsModules,
324
+ nxProjects: e.nxProjects,
325
+ python: e.fileImports.python,
326
+ typescript: e.fileImports.typescript
327
+ }[t];
328
+ }
329
+ async runLevel(e) {
330
+ return {
331
+ nestjsModules: async (e) => this.runNestjsLevel(e),
332
+ nxProjects: (e) => this.runNxLevel(e),
333
+ python: async (e) => this.runPythonImportsLevel(e),
334
+ typescript: async (e) => this.runTypescriptImportsLevel(e)
335
+ }[e.level](e);
336
+ }
337
+ async runNestjsLevel(e) {
338
+ return this.runProjectLevel({
339
+ buildGraph: async (e) => this.boundaryGraphService.buildNestjsGraph(this.moduleGraphService.buildGraph(await this.nestjsProjectService.exploreProject(e), e.name)),
340
+ projects: this.nestjsProjectService.discoverProjects(e.context.selectedProjects),
341
+ rules: e.rules
342
+ });
343
+ }
344
+ runNxLevel(e) {
345
+ let { context: t, rules: n } = e;
346
+ try {
347
+ let e = this.boundaryGraphService.buildNxGraph({
348
+ projects: t.selectedProjects,
349
+ scope: k,
350
+ workingDirectory: t.workingDirectory,
351
+ workspaceGraph: this.workspaceGraphService.buildWorkspaceGraph(t.graph, t.selectedProjects)
352
+ });
353
+ return {
354
+ failures: [],
355
+ violations: this.boundariesService.evaluate({
356
+ graph: e,
357
+ rules: n
358
+ })
359
+ };
360
+ } catch (e) {
361
+ return {
362
+ failures: [this.collectProjectFailure(k, e)],
363
+ violations: []
364
+ };
365
+ }
366
+ }
367
+ async runProjectLevel(e) {
368
+ let t = [], n = [];
369
+ for (let r of e.projects) try {
370
+ let t = await e.buildGraph(r);
371
+ n.push(...this.boundariesService.evaluate({
372
+ graph: t,
373
+ rules: e.rules
374
+ }));
375
+ } catch (e) {
376
+ t.push(this.collectProjectFailure(r.name, e));
377
+ }
378
+ return {
379
+ failures: t,
380
+ violations: n
381
+ };
382
+ }
383
+ async runPythonImportsLevel(e) {
384
+ return this.runProjectLevel({
385
+ buildGraph: (e) => this.boundaryGraphService.buildPythonImportGraph(this.pythonService.buildGraph(e)),
386
+ projects: this.pythonService.discoverProjects(e.context.selectedProjects),
387
+ rules: e.rules
388
+ });
389
+ }
390
+ async runTypescriptImportsLevel(e) {
391
+ return this.runProjectLevel({
392
+ buildGraph: (e) => this.boundaryGraphService.buildTypescriptImportGraph(this.typescriptService.buildGraph(this.typescriptService.buildProgram(e))),
393
+ projects: this.typescriptService.discoverProjects(e.context.selectedProjects),
394
+ rules: e.rules
395
+ });
396
+ }
397
+ async run(e) {
398
+ let { boundaries: t } = e.configuration, n = [];
399
+ for (let r of A) {
400
+ if (!e.enabledGraphTypes.has(this.graphTypeForLevel(r))) continue;
401
+ let i = this.rulesForLevel(t, r);
402
+ i.length > 0 && n.push(await this.runLevel({
403
+ context: e,
404
+ level: r,
405
+ rules: i
406
+ }));
407
+ }
408
+ return {
409
+ failures: n.flatMap((e) => e.failures),
410
+ violations: n.flatMap((e) => e.violations)
411
+ };
412
+ }
413
+ };
414
+ M = C([e(), S("design:paramtypes", [
415
+ E === void 0 ? Object : E,
416
+ j === void 0 ? Object : j,
417
+ c === void 0 ? Object : c,
418
+ u === void 0 ? Object : u,
419
+ i === void 0 ? Object : i,
420
+ o === void 0 ? Object : o,
421
+ m === void 0 ? Object : m
422
+ ])], M);
423
+ //#endregion
424
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/boundary-check/boundary-check.module.ts
425
+ var N = class {};
426
+ N = C([t({
427
+ controllers: [],
428
+ exports: [
429
+ O,
430
+ M,
431
+ j
432
+ ],
433
+ imports: [
434
+ O,
435
+ s,
436
+ l,
437
+ r,
438
+ a,
439
+ p
440
+ ],
441
+ providers: [M, j]
442
+ })], N);
443
+ //#endregion
444
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/run-context/run-context.service.ts
445
+ var P = class {
446
+ configurationService;
447
+ neighborhoodService;
448
+ constructor(e, t) {
449
+ this.configurationService = e, this.neighborhoodService = t;
450
+ }
451
+ async loadProjectConfigurations(e) {
452
+ let t = await Promise.all(e.map(async (e) => [e.name, await this.configurationService.loadProjectConfiguration({ projectRoot: e.absoluteRoot })]));
453
+ return new Map(t);
454
+ }
455
+ resolveEnabledGraphTypes(e) {
456
+ return new Set(h.filter((t) => e[t] !== !1));
457
+ }
458
+ resolveProjectGraphPath(e, t) {
459
+ return e === void 0 ? void 0 : n.resolve(t, e);
460
+ }
461
+ selectProjects(e) {
462
+ return e.projects.filter((t) => this.configurationService.isProjectSelected({
463
+ configuration: e.configuration,
464
+ projectName: t.name,
465
+ projectRoot: n.relative(e.workingDirectory, t.absoluteRoot),
466
+ projectTags: t.tags
467
+ }));
468
+ }
469
+ async build(e) {
470
+ let { mode: t, options: n, workingDirectory: r } = e, i = await this.configurationService.loadConfiguration({
471
+ configurationPath: n.config,
472
+ overrides: {
473
+ exclude: n.exclude,
474
+ include: n.include
475
+ },
476
+ searchDirectory: r,
477
+ selection: {
478
+ projects: n.projects,
479
+ tags: n.tags
480
+ }
481
+ }), a = await this.neighborhoodService.readProjectGraph(this.resolveProjectGraphPath(i.projectGraph, r)), o = this.neighborhoodService.readProjects(a, r), s = await this.loadProjectConfigurations(o);
482
+ return {
483
+ configuration: i,
484
+ enabledGraphTypes: this.resolveEnabledGraphTypes(n),
485
+ graph: a,
486
+ mode: t,
487
+ projectConfigurations: s,
488
+ projects: o,
489
+ selectedProjects: this.selectProjects({
490
+ configuration: i,
491
+ projects: o,
492
+ workingDirectory: r
493
+ }),
494
+ workingDirectory: r
495
+ };
496
+ }
497
+ };
498
+ P = C([e(), S("design:paramtypes", [_ === void 0 ? Object : _, f === void 0 ? Object : f])], P);
499
+ //#endregion
500
+ //#region packages/ic-suite/codependix/codependix-boundaries/src/modules/run-context/run-context.module.ts
501
+ var F = class {};
502
+ F = C([t({
503
+ controllers: [],
504
+ exports: [P],
505
+ imports: [g, d],
506
+ providers: [P]
507
+ })], F);
508
+ //#endregion
509
+ export { A as BOUNDARY_LEVEL_ORDER, O as BoundariesModule, E as BoundariesService, N as BoundaryCheckModule, M as BoundaryCheckService, w as BoundaryCyclesService, j as BoundaryGraphService, D as BoundaryReportService, T as BoundarySelectorService, v as CYCLE_SEPARATOR, F as RunContextModule, P as RunContextService, k as WORKSPACE_SCOPE, y as describeCycle, b as describeDisallowedEdge, x as describeForbiddenEdge };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "author": "Jimmy Paolini",
3
+ "bugs": {
4
+ "url": "https://github.com/organizzolini/codebase/issues"
5
+ },
6
+ "dependencies": {
7
+ "@codependix/configuration": "0.0.3",
8
+ "@codependix/core": "0.0.3",
9
+ "@codependix/file-imports": "0.0.3",
10
+ "@codependix/nestjs-modules": "0.0.3",
11
+ "@codependix/nx-projects": "0.0.3",
12
+ "@nestjs/common": "^11.1.21 || ^12.0.1",
13
+ "reflect-metadata": "^0.2.2"
14
+ },
15
+ "description": "Boundary rule validation, forbid/allow rule enforcement, and cycle detection for Codependix graphs",
16
+ "devDependencies": {
17
+ "@golevelup/ts-vitest": "^4.0.0",
18
+ "@nestjs/testing": "^12.0.1",
19
+ "@nx/eslint-plugin": "^23.0.0",
20
+ "@swc/helpers": "^0.5.21",
21
+ "@types/node": "^26.5.1",
22
+ "@vitest/coverage-v8": "^5.0.0",
23
+ "typescript": "^6.0.3",
24
+ "vitest": "^5.0.0"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/src/index.d.ts",
29
+ "default": "./dist/src/index.js"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "LICENSE",
36
+ "README.md"
37
+ ],
38
+ "homepage": "https://github.com/organizzolini/codebase/tree/main/packages/ic-suite/codependix/codependix-boundaries#readme",
39
+ "keywords": [
40
+ "boundaries",
41
+ "codependix",
42
+ "cycle-detection",
43
+ "linting",
44
+ "rule-enforcement"
45
+ ],
46
+ "license": "MIT",
47
+ "main": "./dist/src/index.js",
48
+ "name": "@codependix/boundaries",
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org"
52
+ },
53
+ "repository": {
54
+ "directory": "packages/ic-suite/codependix/codependix-boundaries",
55
+ "type": "git",
56
+ "url": "git+https://github.com/organizzolini/codebase.git"
57
+ },
58
+ "type": "module",
59
+ "typeCoverage": {
60
+ "atLeast": 100,
61
+ "strict": true
62
+ },
63
+ "types": "./dist/src/index.d.ts",
64
+ "version": "0.0.3"
65
+ }