@webpieces/nx-webpieces-rules 0.3.196 → 0.3.197
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/package.json +6 -6
- package/src/executors/validate-catch-error-pattern/schema.json +2 -2
- package/src/executors/validate-code/schema.json +13 -13
- package/src/executors/validate-dtos/schema.json +2 -2
- package/src/executors/validate-modified-files/schema.json +3 -3
- package/src/executors/validate-modified-methods/schema.json +2 -2
- package/src/executors/validate-new-methods/schema.json +2 -2
- package/src/executors/validate-no-any-unknown/schema.json +2 -2
- package/src/executors/validate-no-destructure/schema.json +2 -2
- package/src/executors/validate-no-direct-api-resolver/schema.json +2 -2
- package/src/executors/validate-no-file-import-cycles/executor.d.ts +2 -2
- package/src/executors/validate-no-file-import-cycles/executor.js +1 -1
- package/src/executors/validate-no-file-import-cycles/executor.js.map +1 -1
- package/src/executors/validate-no-implicit-any/schema.json +2 -2
- package/src/executors/validate-no-inline-types/schema.json +2 -2
- package/src/executors/validate-no-unmanaged-exceptions/schema.json +2 -2
- package/src/executors/validate-prisma-converters/schema.json +2 -2
- package/src/executors/validate-return-types/schema.json +2 -2
- package/src/executors/validate-ts-in-src/executor.d.ts +3 -3
- package/src/executors/validate-ts-in-src/executor.js +7 -88
- package/src/executors/validate-ts-in-src/executor.js.map +1 -1
- package/src/executors/validate-ts-in-src/schema.json +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.197",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webpieces/ai-hook-rules": "0.3.
|
|
22
|
-
"@webpieces/code-rules": "0.3.
|
|
23
|
-
"@webpieces/eslint-rules": "0.3.
|
|
24
|
-
"@webpieces/pr-gate": "0.3.
|
|
25
|
-
"@webpieces/rules-config": "0.3.
|
|
21
|
+
"@webpieces/ai-hook-rules": "0.3.197",
|
|
22
|
+
"@webpieces/code-rules": "0.3.197",
|
|
23
|
+
"@webpieces/eslint-rules": "0.3.197",
|
|
24
|
+
"@webpieces/pr-gate": "0.3.197",
|
|
25
|
+
"@webpieces/rules-config": "0.3.197",
|
|
26
26
|
"madge": "8.0.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only catch blocks on changed lines in diff.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only catch blocks on changed lines in diff. NEW_AND_MODIFIED_FILES: all catch blocks in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"mode": {
|
|
17
17
|
"type": "string",
|
|
18
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
18
|
+
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all methods in modified files.",
|
|
19
19
|
"default": "NEW_AND_MODIFIED_METHODS"
|
|
20
20
|
},
|
|
21
21
|
"disableAllowed": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"mode": {
|
|
42
42
|
"type": "string",
|
|
43
|
-
"description": "OFF: skip validation.
|
|
44
|
-
"default": "
|
|
43
|
+
"description": "OFF: skip validation. NEW_AND_MODIFIED_FILES: all code in modified files.",
|
|
44
|
+
"default": "NEW_AND_MODIFIED_FILES"
|
|
45
45
|
},
|
|
46
46
|
"disableAllowed": {
|
|
47
47
|
"type": "boolean",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"properties": {
|
|
61
61
|
"mode": {
|
|
62
62
|
"type": "string",
|
|
63
|
-
"description": "OFF: skip return type validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
63
|
+
"description": "OFF: skip return type validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all methods in modified files.",
|
|
64
64
|
"default": "OFF"
|
|
65
65
|
},
|
|
66
66
|
"disableAllowed": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"properties": {
|
|
81
81
|
"mode": {
|
|
82
82
|
"type": "string",
|
|
83
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods. NEW_AND_MODIFIED_METHODS: new + modified methods.
|
|
83
|
+
"description": "OFF: skip validation. NEW_METHODS: only new methods. NEW_AND_MODIFIED_METHODS: new + modified methods. NEW_AND_MODIFIED_FILES: all in modified files. Disallows inline type literals like { x: number } - use named types instead.",
|
|
84
84
|
"default": "OFF"
|
|
85
85
|
},
|
|
86
86
|
"disableAllowed": {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"properties": {
|
|
101
101
|
"mode": {
|
|
102
102
|
"type": "string",
|
|
103
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
103
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows `any` and `unknown` TypeScript keywords.",
|
|
104
104
|
"default": "OFF"
|
|
105
105
|
},
|
|
106
106
|
"disableAllowed": {
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"properties": {
|
|
121
121
|
"mode": {
|
|
122
122
|
"type": "string",
|
|
123
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only implicit-any on changed lines.
|
|
123
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only implicit-any on changed lines. NEW_AND_MODIFIED_FILES: all implicit-any in modified files.",
|
|
124
124
|
"default": "OFF"
|
|
125
125
|
},
|
|
126
126
|
"disableAllowed": {
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"properties": {
|
|
141
141
|
"mode": {
|
|
142
142
|
"type": "string",
|
|
143
|
-
"description": "OFF: skip validation. MODIFIED_CLASS: only validate Dto classes with changed lines.
|
|
143
|
+
"description": "OFF: skip validation. MODIFIED_CLASS: only validate Dto classes with changed lines. NEW_AND_MODIFIED_FILES: validate all Dtos in modified files.",
|
|
144
144
|
"default": "OFF"
|
|
145
145
|
},
|
|
146
146
|
"disableAllowed": {
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"properties": {
|
|
170
170
|
"mode": {
|
|
171
171
|
"type": "string",
|
|
172
|
-
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: validate new/modified methods in converters + changed lines in non-converters.
|
|
172
|
+
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: validate new/modified methods in converters + changed lines in non-converters. NEW_AND_MODIFIED_FILES: validate all methods in modified files.",
|
|
173
173
|
"default": "OFF"
|
|
174
174
|
},
|
|
175
175
|
"disableAllowed": {
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"properties": {
|
|
204
204
|
"mode": {
|
|
205
205
|
"type": "string",
|
|
206
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
206
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows destructuring patterns.",
|
|
207
207
|
"default": "OFF"
|
|
208
208
|
},
|
|
209
209
|
"disableAllowed": {
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"properties": {
|
|
224
224
|
"mode": {
|
|
225
225
|
"type": "string",
|
|
226
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only catch blocks on changed lines.
|
|
226
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only catch blocks on changed lines. NEW_AND_MODIFIED_FILES: all catch blocks in modified files.",
|
|
227
227
|
"default": "OFF"
|
|
228
228
|
},
|
|
229
229
|
"disableAllowed": {
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
"properties": {
|
|
244
244
|
"mode": {
|
|
245
245
|
"type": "string",
|
|
246
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
246
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows try/catch blocks.",
|
|
247
247
|
"default": "OFF"
|
|
248
248
|
},
|
|
249
249
|
"disableAllowed": {
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
"properties": {
|
|
264
264
|
"mode": {
|
|
265
265
|
"type": "string",
|
|
266
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_METHODS: violations in new/modified method/route scopes.
|
|
266
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_METHODS: violations in new/modified method/route scopes. NEW_AND_MODIFIED_FILES: all in modified files.",
|
|
267
267
|
"default": "OFF"
|
|
268
268
|
},
|
|
269
269
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CLASS", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CLASS: only validate Dto classes with changed lines.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CLASS", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CLASS: only validate Dto classes with changed lines. NEW_AND_MODIFIED_FILES: validate all Dtos in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"prismaSchemaPath": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"mode": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"enum": ["OFF", "
|
|
15
|
-
"description": "OFF: skip validation.
|
|
16
|
-
"default": "
|
|
14
|
+
"enum": ["OFF", "NEW_AND_MODIFIED_FILES"],
|
|
15
|
+
"description": "OFF: skip validation. NEW_AND_MODIFIED_FILES: validate all code in modified files.",
|
|
16
|
+
"default": "NEW_AND_MODIFIED_FILES"
|
|
17
17
|
},
|
|
18
18
|
"disableAllowed": {
|
|
19
19
|
"type": "boolean",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"mode": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "
|
|
15
|
-
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
14
|
+
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
15
|
+
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all methods in modified files.",
|
|
16
16
|
"default": "NEW_AND_MODIFIED_METHODS"
|
|
17
17
|
},
|
|
18
18
|
"disableAllowed": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"mode": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "
|
|
15
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
14
|
+
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
15
|
+
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all methods in modified files.",
|
|
16
16
|
"default": "NEW_AND_MODIFIED_METHODS"
|
|
17
17
|
},
|
|
18
18
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows `any` and `unknown` TypeScript keywords.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows destructuring patterns.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_METHODS", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_METHODS: violations in new/modified method/route scopes.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_METHODS: violations in new/modified method/route scopes. NEW_AND_MODIFIED_FILES: all in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Config (webpieces.config.json, rule key `no-file-import-cycles`):
|
|
14
14
|
* "no-file-import-cycles": {
|
|
15
|
-
* "mode": "
|
|
15
|
+
* "mode": "RUN_EVERY_TIME", // "OFF" disables the gate everywhere
|
|
16
16
|
* "ignoreModifiedUntilEpoch": 1771931925, // epoch SECONDS; while now < epoch,
|
|
17
17
|
* // cycles are reported but the gate PASSES
|
|
18
18
|
* // (warn, don't fail). After it, fails again.
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* Usage: nx run <project>:validate-no-file-import-cycles
|
|
31
31
|
*/
|
|
32
32
|
import type { ExecutorContext } from '@nx/devkit';
|
|
33
|
-
export type ValidateNoFileImportCyclesMode = '
|
|
33
|
+
export type ValidateNoFileImportCyclesMode = 'RUN_EVERY_TIME' | 'OFF';
|
|
34
34
|
export interface ValidateNoFileImportCyclesOptions {
|
|
35
35
|
}
|
|
36
36
|
export interface ExecutorResult {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Config (webpieces.config.json, rule key `no-file-import-cycles`):
|
|
15
15
|
* "no-file-import-cycles": {
|
|
16
|
-
* "mode": "
|
|
16
|
+
* "mode": "RUN_EVERY_TIME", // "OFF" disables the gate everywhere
|
|
17
17
|
* "ignoreModifiedUntilEpoch": 1771931925, // epoch SECONDS; while now < epoch,
|
|
18
18
|
* // cycles are reported but the gate PASSES
|
|
19
19
|
* // (warn, don't fail). After it, fails again.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — see NEEDED_CHANGES.md #1), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `ignoreModifiedUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"ON\", // \"OFF\" disables the gate everywhere\n * \"ignoreModifiedUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@kami/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate, shouldSkipRule } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'ON' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the universal escape hatches: the ignoreModifiedUntilEpoch\n * grace window and ignoreRuleWhileOnBranch. Logs a one-line explanation when a\n * hatch is active.\n */\nfunction isFailingActive(epoch: number | undefined, branch: string | undefined): boolean {\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(\n `\\n⏳ no-file-import-cycles: ${skip.reason}.` +\n '\\n Cycles will be reported but NOT fail the build.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@mealco-internal/kami\" → [\"libraries/kami/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\nfunction buildMadgeOptions(ignoreTypeOnly: boolean, excludePackages: string[], workspaceRoot: string): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES];\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(`^${escapeRegex(dir)}(/|$)`);\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To time-box a known cycle, a human can set \"ignoreModifiedUntilEpoch\"');\n console.error(`(epoch seconds) on the \"${RULE_NAME}\" rule in webpieces.config.json.`);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const branch = rule?.options['ignoreRuleWhileOnBranch'] as string | undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n const result = await madge(projectRoot, buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root));\n const cycles = result.circular();\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window or branch hatch active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch, branch) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — see NEEDED_CHANGES.md #1), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `ignoreModifiedUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"RUN_EVERY_TIME\", // \"OFF\" disables the gate everywhere\n * \"ignoreModifiedUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@kami/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate, shouldSkipRule } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'RUN_EVERY_TIME' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the universal escape hatches: the ignoreModifiedUntilEpoch\n * grace window and ignoreRuleWhileOnBranch. Logs a one-line explanation when a\n * hatch is active.\n */\nfunction isFailingActive(epoch: number | undefined, branch: string | undefined): boolean {\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(\n `\\n⏳ no-file-import-cycles: ${skip.reason}.` +\n '\\n Cycles will be reported but NOT fail the build.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@mealco-internal/kami\" → [\"libraries/kami/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\nfunction buildMadgeOptions(ignoreTypeOnly: boolean, excludePackages: string[], workspaceRoot: string): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES];\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(`^${escapeRegex(dir)}(/|$)`);\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To time-box a known cycle, a human can set \"ignoreModifiedUntilEpoch\"');\n console.error(`(epoch seconds) on the \"${RULE_NAME}\" rule in webpieces.config.json.`);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const branch = rule?.options['ignoreRuleWhileOnBranch'] as string | undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n const result = await madge(projectRoot, buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root));\n const cycles = result.circular();\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window or branch hatch active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch, branch) };\n}\n"]}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only implicit-any on changed lines in diff.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only implicit-any on changed lines in diff. NEW_AND_MODIFIED_FILES: all implicit-any in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "
|
|
10
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
9
|
+
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all inline types in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "MODIFIED_CODE", "
|
|
10
|
-
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff.
|
|
9
|
+
"enum": ["OFF", "MODIFIED_CODE", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. MODIFIED_CODE: only changed lines in diff. NEW_AND_MODIFIED_FILES: all in modified files. Disallows try/catch blocks.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "NEW_AND_MODIFIED_METHODS", "
|
|
10
|
-
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: validate methods that are new or have changed lines.
|
|
9
|
+
"enum": ["OFF", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. NEW_AND_MODIFIED_METHODS: validate methods that are new or have changed lines. NEW_AND_MODIFIED_FILES: validate all methods in modified files.",
|
|
11
11
|
"default": "OFF"
|
|
12
12
|
},
|
|
13
13
|
"schemaPath": {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "
|
|
10
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes.
|
|
9
|
+
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. NEW_AND_MODIFIED_FILES: all methods in modified files.",
|
|
11
11
|
"default": "NEW_METHODS"
|
|
12
12
|
},
|
|
13
13
|
"disableAllowed": {
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* `excludePaths` is holistic — its bare dir names and globs exempt files
|
|
11
11
|
* from BOTH layers. Defaults exempt **\/*.d.ts and **\/jest.config.ts.
|
|
12
12
|
*
|
|
13
|
-
* Only the files changed vs the base branch are validated (
|
|
13
|
+
* Only the files changed vs the base branch are validated (NEW_AND_MODIFIED_FILES),
|
|
14
14
|
* so the rule applies cleanly to legacy projects — pre-existing orphan files
|
|
15
15
|
* are grandfathered and only newly-touched files are checked.
|
|
16
16
|
*
|
|
17
17
|
* Configurable via webpieces.config.json:
|
|
18
18
|
* "validate-ts-in-src": {
|
|
19
|
-
* "mode": "
|
|
19
|
+
* "mode": "NEW_AND_MODIFIED_FILES", // "OFF" disables the rule
|
|
20
20
|
* "excludePaths": [...], // dir names + globs, e.g. "**\/codegen.ts"
|
|
21
21
|
* "allowedRootFiles": [...]
|
|
22
22
|
* }
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Usage: nx run architecture:validate-ts-in-src
|
|
25
25
|
*/
|
|
26
26
|
import type { ExecutorContext } from '@nx/devkit';
|
|
27
|
-
export type ValidateTsInSrcMode = 'OFF' | '
|
|
27
|
+
export type ValidateTsInSrcMode = 'OFF' | 'NEW_AND_MODIFIED_FILES';
|
|
28
28
|
export interface ValidateTsInSrcOptions {
|
|
29
29
|
mode?: ValidateTsInSrcMode;
|
|
30
30
|
excludePaths?: string[];
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* `excludePaths` is holistic — its bare dir names and globs exempt files
|
|
12
12
|
* from BOTH layers. Defaults exempt **\/*.d.ts and **\/jest.config.ts.
|
|
13
13
|
*
|
|
14
|
-
* Only the files changed vs the base branch are validated (
|
|
14
|
+
* Only the files changed vs the base branch are validated (NEW_AND_MODIFIED_FILES),
|
|
15
15
|
* so the rule applies cleanly to legacy projects — pre-existing orphan files
|
|
16
16
|
* are grandfathered and only newly-touched files are checked.
|
|
17
17
|
*
|
|
18
18
|
* Configurable via webpieces.config.json:
|
|
19
19
|
* "validate-ts-in-src": {
|
|
20
|
-
* "mode": "
|
|
20
|
+
* "mode": "NEW_AND_MODIFIED_FILES", // "OFF" disables the rule
|
|
21
21
|
* "excludePaths": [...], // dir names + globs, e.g. "**\/codegen.ts"
|
|
22
22
|
* "allowedRootFiles": [...]
|
|
23
23
|
* }
|
|
@@ -29,7 +29,6 @@ exports.default = runExecutor;
|
|
|
29
29
|
const tslib_1 = require("tslib");
|
|
30
30
|
const devkit_1 = require("@nx/devkit");
|
|
31
31
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
32
|
-
const child_process_1 = require("child_process");
|
|
33
32
|
const path = tslib_1.__importStar(require("path"));
|
|
34
33
|
const DEFAULT_EXCLUDE_PATHS = [
|
|
35
34
|
'node_modules', 'dist', '.nx', '.git',
|
|
@@ -64,86 +63,6 @@ async function getProjectRoots(workspaceRoot) {
|
|
|
64
63
|
}
|
|
65
64
|
return roots;
|
|
66
65
|
}
|
|
67
|
-
function isTestFile(filePath) {
|
|
68
|
-
return filePath.includes('.spec.ts') ||
|
|
69
|
-
filePath.includes('.test.ts') ||
|
|
70
|
-
filePath.includes('__tests__/');
|
|
71
|
-
}
|
|
72
|
-
// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
|
|
73
|
-
function getChangedTsFiles(workspaceRoot, base, head) {
|
|
74
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
75
|
-
try {
|
|
76
|
-
const diffTarget = head ? `${base} ${head}` : base;
|
|
77
|
-
const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
78
|
-
cwd: workspaceRoot,
|
|
79
|
-
encoding: 'utf-8',
|
|
80
|
-
});
|
|
81
|
-
const changedFiles = output
|
|
82
|
-
.trim()
|
|
83
|
-
.split('\n')
|
|
84
|
-
.filter((f) => f && !isTestFile(f));
|
|
85
|
-
if (!head) {
|
|
86
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
87
|
-
try {
|
|
88
|
-
const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
89
|
-
cwd: workspaceRoot,
|
|
90
|
-
encoding: 'utf-8',
|
|
91
|
-
});
|
|
92
|
-
const untrackedFiles = untrackedOutput
|
|
93
|
-
.trim()
|
|
94
|
-
.split('\n')
|
|
95
|
-
.filter((f) => f && !isTestFile(f));
|
|
96
|
-
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
97
|
-
return Array.from(allFiles);
|
|
98
|
-
// webpieces-disable catch-error-pattern -- intentional swallow; git ls-files failure falls back to staged-only list
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
//const error = toError(err);
|
|
102
|
-
return changedFiles;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return changedFiles;
|
|
106
|
-
// webpieces-disable catch-error-pattern -- intentional swallow; git diff failure returns empty list
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
//const error = toError(err);
|
|
110
|
-
return [];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function detectBase(workspaceRoot) {
|
|
114
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
115
|
-
try {
|
|
116
|
-
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
117
|
-
cwd: workspaceRoot,
|
|
118
|
-
encoding: 'utf-8',
|
|
119
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
|
-
}).trim();
|
|
121
|
-
if (mergeBase) {
|
|
122
|
-
return mergeBase;
|
|
123
|
-
}
|
|
124
|
-
// webpieces-disable catch-error-pattern -- intentional swallow; try local main as fallback
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
//const error = toError(err);
|
|
128
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
129
|
-
try {
|
|
130
|
-
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
131
|
-
cwd: workspaceRoot,
|
|
132
|
-
encoding: 'utf-8',
|
|
133
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
134
|
-
}).trim();
|
|
135
|
-
if (mergeBase) {
|
|
136
|
-
return mergeBase;
|
|
137
|
-
}
|
|
138
|
-
// webpieces-disable catch-error-pattern -- intentional swallow; no base found is handled by caller
|
|
139
|
-
}
|
|
140
|
-
catch (err2) {
|
|
141
|
-
//const error2 = toError(err2);
|
|
142
|
-
// Ignore
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
66
|
function checkSingleFileLayerOne(relPath, projectRoots, workspaceRoot, excludePaths) {
|
|
148
67
|
if ((0, rules_config_1.isPathExcluded)(relPath, excludePaths))
|
|
149
68
|
return null;
|
|
@@ -219,11 +138,11 @@ function reportLayerTwoFailure(violations) {
|
|
|
219
138
|
console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\n');
|
|
220
139
|
}
|
|
221
140
|
async function runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles) {
|
|
222
|
-
console.log('\n📁 Validating TypeScript files are in src/ and owned by a project (
|
|
141
|
+
console.log('\n📁 Validating TypeScript files are in src/ and owned by a project (NEW_AND_MODIFIED_FILES mode)\n');
|
|
223
142
|
let base = process.env['NX_BASE'];
|
|
224
143
|
const head = process.env['NX_HEAD'];
|
|
225
144
|
if (!base) {
|
|
226
|
-
base = detectBase(workspaceRoot) ?? undefined;
|
|
145
|
+
base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
|
|
227
146
|
if (!base) {
|
|
228
147
|
console.log('\n⏭️ Skipping validate-ts-in-src validation (could not detect base branch)\n');
|
|
229
148
|
return { success: true };
|
|
@@ -233,7 +152,7 @@ async function runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFile
|
|
|
233
152
|
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
234
153
|
console.log('');
|
|
235
154
|
const projectRoots = await getProjectRoots(workspaceRoot);
|
|
236
|
-
const changedFiles =
|
|
155
|
+
const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
|
|
237
156
|
if (changedFiles.length === 0) {
|
|
238
157
|
console.log('✅ No TypeScript files changed\n');
|
|
239
158
|
return { success: true };
|
|
@@ -267,7 +186,7 @@ async function runExecutor(_nxOptions, context) {
|
|
|
267
186
|
// and validate-code — via @webpieces/rules-config.
|
|
268
187
|
const shared = (0, rules_config_1.loadAndValidate)(context.root).resolved;
|
|
269
188
|
const rule = shared.rules.get('validate-ts-in-src');
|
|
270
|
-
const rawMode = rule?.options['mode'] ?? '
|
|
189
|
+
const rawMode = rule?.options['mode'] ?? 'NEW_AND_MODIFIED_FILES';
|
|
271
190
|
const epoch = rule?.options['ignoreModifiedUntilEpoch'];
|
|
272
191
|
const branch = rule?.options['ignoreRuleWhileOnBranch'];
|
|
273
192
|
const effectiveMode = resolveMode(rawMode, epoch, branch);
|
|
@@ -278,7 +197,7 @@ async function runExecutor(_nxOptions, context) {
|
|
|
278
197
|
const workspaceRoot = context.root;
|
|
279
198
|
const excludePaths = rule?.options['excludePaths'] ?? DEFAULT_EXCLUDE_PATHS;
|
|
280
199
|
const allowedRootFiles = rule?.options['allowedRootFiles'] ?? DEFAULT_ALLOWED_ROOT_FILES;
|
|
281
|
-
// Only
|
|
200
|
+
// Only NEW_AND_MODIFIED_FILES remains once OFF is handled above — validate just the
|
|
282
201
|
// files changed vs the base branch (legacy-friendly; no whole-workspace scan).
|
|
283
202
|
return runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles);
|
|
284
203
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-ts-in-src/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate TypeScript Files in src/ Executor\n *\n * Two-layer rule:\n * Layer 1: every .ts file inside an Nx project must live under src/\n * Layer 2: every .ts file anywhere in the workspace must belong to some\n * Nx project. Orphan files (at workspace root or in a non-project\n * directory) fail the rule unless explicitly allowlisted.\n *\n * `excludePaths` is holistic — its bare dir names and globs exempt files\n * from BOTH layers. Defaults exempt **\\/*.d.ts and **\\/jest.config.ts.\n *\n * Only the files changed vs the base branch are validated (MODIFIED_FILES),\n * so the rule applies cleanly to legacy projects — pre-existing orphan files\n * are grandfathered and only newly-touched files are checked.\n *\n * Configurable via webpieces.config.json:\n * \"validate-ts-in-src\": {\n * \"mode\": \"MODIFIED_FILES\", // \"OFF\" disables the rule\n * \"excludePaths\": [...], // dir names + globs, e.g. \"**\\/codegen.ts\"\n * \"allowedRootFiles\": [...]\n * }\n *\n * Usage: nx run architecture:validate-ts-in-src\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadAndValidate, isPathExcluded, shouldSkipRule } from '@webpieces/rules-config';\nimport { execSync } from 'child_process';\nimport * as path from 'path';\n\nexport type ValidateTsInSrcMode = 'OFF' | 'MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n excludePaths?: string[];\n allowedRootFiles?: string[];\n ignoreModifiedUntilEpoch?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_EXCLUDE_PATHS: string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n 'architecture', 'tmp', 'scripts',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\nconst DEFAULT_ALLOWED_ROOT_FILES: string[] = ['jest.setup.ts'];\n\nclass LayerOneViolation {\n filePath: string;\n projectName: string;\n\n constructor(filePath: string, projectName: string) {\n this.filePath = filePath;\n this.projectName = projectName;\n }\n}\n\nclass LayerTwoViolation {\n filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n}\n\nasync function getProjectRoots(workspaceRoot: string): Promise<string[]> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const roots: string[] = [];\n for (const cfg of Object.values(projectsConfig.projects)) {\n if (cfg.root === '' || cfg.root === '.') continue;\n if (cfg.root === 'architecture') continue;\n roots.push(path.join(workspaceRoot, cfg.root));\n }\n return roots;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') ||\n filePath.includes('.test.ts') ||\n filePath.includes('__tests__/');\n}\n\n// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths\nfunction getChangedTsFiles(workspaceRoot: string, base: string, head?: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n // webpieces-disable catch-error-pattern -- intentional swallow; git ls-files failure falls back to staged-only list\n } catch (err: unknown) {\n //const error = toError(err);\n return changedFiles;\n }\n }\n\n return changedFiles;\n // webpieces-disable catch-error-pattern -- intentional swallow; git diff failure returns empty list\n } catch (err: unknown) {\n //const error = toError(err);\n return [];\n }\n}\n\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; try local main as fallback\n } catch (err: unknown) {\n //const error = toError(err);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; no base found is handled by caller\n } catch (err2: unknown) {\n //const error2 = toError(err2);\n // Ignore\n }\n }\n return null;\n}\n\nfunction checkSingleFileLayerOne(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep) || fullPath === projectRoot) {\n const relToProject = path.relative(projectRoot, fullPath);\n if (relToProject.startsWith('src' + path.sep) || relToProject === 'src') return null;\n const projectName = path.relative(workspaceRoot, projectRoot);\n return new LayerOneViolation(relPath, projectName);\n }\n }\n return null; // not in any project — that's layer 2's concern\n}\n\nfunction checkSingleFileLayerTwo(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n allowedRootFiles: string[],\n excludePaths: string[],\n): LayerTwoViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const parts = relPath.split(path.sep);\n if (parts.length === 1 && allowedRootFiles.includes(parts[0] ?? '')) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep)) return null; // owned by a project\n }\n return new LayerTwoViolation(relPath);\n}\n\nfunction resolveMode(\n normalMode: ValidateTsInSrcMode,\n epoch: number | undefined,\n branch: string | undefined,\n): ValidateTsInSrcMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n // Honor the universal escape hatches: skip while on a named branch or until epoch.\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping validate-ts-in-src validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nfunction reportLayerOneFailure(violations: LayerOneViolation[]): void {\n console.error('❌ TypeScript files found outside src/ directory!\\n');\n console.error('All .ts source files must be inside the project\\'s src/ directory.');\n console.error('This enforces the standard project structure:\\n');\n console.error(' packages/{category}/{name}/');\n console.error(' ├── src/ ← ALL .ts files here');\n console.error(' ├── package.json');\n console.error(' ├── project.json');\n console.error(' └── tsconfig.json\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the .ts file(s) into the src/ directory');\n console.error(' (b) Add a glob/dir to validate-ts-in-src.excludePaths in');\n console.error(' webpieces.config.json (e.g. \"**/codegen.ts\"). Defaults already');\n console.error(' exempt **/*.d.ts and **/jest.config.ts.\\n');\n}\n\nfunction reportLayerTwoFailure(violations: LayerTwoViolation[]): void {\n console.error('❌ TypeScript files found outside any Nx project!\\n');\n console.error('Every .ts file must belong to an Nx project so it is compiled,');\n console.error('linted, and tested under a known project config. Orphan files are');\n console.error('invisible to the build graph and will rot.\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the file into an existing project\\'s src/ directory');\n console.error(' (b) Create a new project (add project.json) that owns the directory');\n console.error(' (c) Add the containing top-level directory to validate-ts-in-src.excludePaths');\n console.error(' in nx.json targetDefaults, or add the filename to allowedRootFiles');\n console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\\n');\n}\n\nasync function runModifiedFilesMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project (MODIFIED_FILES mode)\\n');\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping validate-ts-in-src validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n const changedFiles = getChangedTsFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed\\n');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const layerOneViolations: LayerOneViolation[] = [];\n const layerTwoViolations: LayerTwoViolation[] = [];\n\n for (const relPath of changedFiles) {\n const l1 = checkSingleFileLayerOne(relPath, projectRoots, workspaceRoot, excludePaths);\n if (l1) layerOneViolations.push(l1);\n const l2 = checkSingleFileLayerTwo(relPath, projectRoots, workspaceRoot, allowedRootFiles, excludePaths);\n if (l2) layerTwoViolations.push(l2);\n }\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All changed .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nexport default async function runExecutor(\n _nxOptions: ValidateTsInSrcOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n // Config comes from webpieces.config.json — same source as ai-hooks\n // and validate-code — via @webpieces/rules-config.\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get('validate-ts-in-src');\n\n const rawMode = (rule?.options['mode'] as ValidateTsInSrcMode | undefined) ?? 'MODIFIED_FILES';\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const branch = rule?.options['ignoreRuleWhileOnBranch'] as string | undefined;\n const effectiveMode = resolveMode(rawMode, epoch, branch);\n\n if (effectiveMode === 'OFF' || (rule && rule.isOff)) {\n console.log('\\n⏭️ Skipping validate-ts-in-src (mode: OFF)\\n');\n return { success: true };\n }\n\n const workspaceRoot = context.root;\n const excludePaths =\n (rule?.options['excludePaths'] as string[] | undefined) ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles =\n (rule?.options['allowedRootFiles'] as string[] | undefined) ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n // Only MODIFIED_FILES remains once OFF is handled above — validate just the\n // files changed vs the base branch (legacy-friendly; no whole-workspace scan).\n return runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-ts-in-src/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate TypeScript Files in src/ Executor\n *\n * Two-layer rule:\n * Layer 1: every .ts file inside an Nx project must live under src/\n * Layer 2: every .ts file anywhere in the workspace must belong to some\n * Nx project. Orphan files (at workspace root or in a non-project\n * directory) fail the rule unless explicitly allowlisted.\n *\n * `excludePaths` is holistic — its bare dir names and globs exempt files\n * from BOTH layers. Defaults exempt **\\/*.d.ts and **\\/jest.config.ts.\n *\n * Only the files changed vs the base branch are validated (NEW_AND_MODIFIED_FILES),\n * so the rule applies cleanly to legacy projects — pre-existing orphan files\n * are grandfathered and only newly-touched files are checked.\n *\n * Configurable via webpieces.config.json:\n * \"validate-ts-in-src\": {\n * \"mode\": \"NEW_AND_MODIFIED_FILES\", // \"OFF\" disables the rule\n * \"excludePaths\": [...], // dir names + globs, e.g. \"**\\/codegen.ts\"\n * \"allowedRootFiles\": [...]\n * }\n *\n * Usage: nx run architecture:validate-ts-in-src\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadAndValidate, isPathExcluded, shouldSkipRule, detectBase, getChangedFiles } from '@webpieces/rules-config';\nimport * as path from 'path';\n\nexport type ValidateTsInSrcMode = 'OFF' | 'NEW_AND_MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n excludePaths?: string[];\n allowedRootFiles?: string[];\n ignoreModifiedUntilEpoch?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_EXCLUDE_PATHS: string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n 'architecture', 'tmp', 'scripts',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\nconst DEFAULT_ALLOWED_ROOT_FILES: string[] = ['jest.setup.ts'];\n\nclass LayerOneViolation {\n filePath: string;\n projectName: string;\n\n constructor(filePath: string, projectName: string) {\n this.filePath = filePath;\n this.projectName = projectName;\n }\n}\n\nclass LayerTwoViolation {\n filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n}\n\nasync function getProjectRoots(workspaceRoot: string): Promise<string[]> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const roots: string[] = [];\n for (const cfg of Object.values(projectsConfig.projects)) {\n if (cfg.root === '' || cfg.root === '.') continue;\n if (cfg.root === 'architecture') continue;\n roots.push(path.join(workspaceRoot, cfg.root));\n }\n return roots;\n}\n\nfunction checkSingleFileLayerOne(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep) || fullPath === projectRoot) {\n const relToProject = path.relative(projectRoot, fullPath);\n if (relToProject.startsWith('src' + path.sep) || relToProject === 'src') return null;\n const projectName = path.relative(workspaceRoot, projectRoot);\n return new LayerOneViolation(relPath, projectName);\n }\n }\n return null; // not in any project — that's layer 2's concern\n}\n\nfunction checkSingleFileLayerTwo(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n allowedRootFiles: string[],\n excludePaths: string[],\n): LayerTwoViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const parts = relPath.split(path.sep);\n if (parts.length === 1 && allowedRootFiles.includes(parts[0] ?? '')) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep)) return null; // owned by a project\n }\n return new LayerTwoViolation(relPath);\n}\n\nfunction resolveMode(\n normalMode: ValidateTsInSrcMode,\n epoch: number | undefined,\n branch: string | undefined,\n): ValidateTsInSrcMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n // Honor the universal escape hatches: skip while on a named branch or until epoch.\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping validate-ts-in-src validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nfunction reportLayerOneFailure(violations: LayerOneViolation[]): void {\n console.error('❌ TypeScript files found outside src/ directory!\\n');\n console.error('All .ts source files must be inside the project\\'s src/ directory.');\n console.error('This enforces the standard project structure:\\n');\n console.error(' packages/{category}/{name}/');\n console.error(' ├── src/ ← ALL .ts files here');\n console.error(' ├── package.json');\n console.error(' ├── project.json');\n console.error(' └── tsconfig.json\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the .ts file(s) into the src/ directory');\n console.error(' (b) Add a glob/dir to validate-ts-in-src.excludePaths in');\n console.error(' webpieces.config.json (e.g. \"**/codegen.ts\"). Defaults already');\n console.error(' exempt **/*.d.ts and **/jest.config.ts.\\n');\n}\n\nfunction reportLayerTwoFailure(violations: LayerTwoViolation[]): void {\n console.error('❌ TypeScript files found outside any Nx project!\\n');\n console.error('Every .ts file must belong to an Nx project so it is compiled,');\n console.error('linted, and tested under a known project config. Orphan files are');\n console.error('invisible to the build graph and will rot.\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the file into an existing project\\'s src/ directory');\n console.error(' (b) Create a new project (add project.json) that owns the directory');\n console.error(' (c) Add the containing top-level directory to validate-ts-in-src.excludePaths');\n console.error(' in nx.json targetDefaults, or add the filename to allowedRootFiles');\n console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\\n');\n}\n\nasync function runModifiedFilesMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project (NEW_AND_MODIFIED_FILES mode)\\n');\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping validate-ts-in-src validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed\\n');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const layerOneViolations: LayerOneViolation[] = [];\n const layerTwoViolations: LayerTwoViolation[] = [];\n\n for (const relPath of changedFiles) {\n const l1 = checkSingleFileLayerOne(relPath, projectRoots, workspaceRoot, excludePaths);\n if (l1) layerOneViolations.push(l1);\n const l2 = checkSingleFileLayerTwo(relPath, projectRoots, workspaceRoot, allowedRootFiles, excludePaths);\n if (l2) layerTwoViolations.push(l2);\n }\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All changed .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nexport default async function runExecutor(\n _nxOptions: ValidateTsInSrcOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n // Config comes from webpieces.config.json — same source as ai-hooks\n // and validate-code — via @webpieces/rules-config.\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get('validate-ts-in-src');\n\n const rawMode = (rule?.options['mode'] as ValidateTsInSrcMode | undefined) ?? 'NEW_AND_MODIFIED_FILES';\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const branch = rule?.options['ignoreRuleWhileOnBranch'] as string | undefined;\n const effectiveMode = resolveMode(rawMode, epoch, branch);\n\n if (effectiveMode === 'OFF' || (rule && rule.isOff)) {\n console.log('\\n⏭️ Skipping validate-ts-in-src (mode: OFF)\\n');\n return { success: true };\n }\n\n const workspaceRoot = context.root;\n const excludePaths =\n (rule?.options['excludePaths'] as string[] | undefined) ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles =\n (rule?.options['allowedRootFiles'] as string[] | undefined) ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n // Only NEW_AND_MODIFIED_FILES remains once OFF is handled above — validate just the\n // files changed vs the base branch (legacy-friendly; no whole-workspace scan).\n return runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles);\n}\n"]}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"mode": {
|
|
8
8
|
"type": "string",
|
|
9
|
-
"enum": ["
|
|
10
|
-
"default": "
|
|
11
|
-
"description": "
|
|
9
|
+
"enum": ["OFF", "NEW_AND_MODIFIED_FILES"],
|
|
10
|
+
"default": "NEW_AND_MODIFIED_FILES",
|
|
11
|
+
"description": "OFF = skip validation, NEW_AND_MODIFIED_FILES = only check new/changed .ts files from git diff (legacy-friendly; pre-existing files are grandfathered). The whole-workspace 'ON' scan was removed."
|
|
12
12
|
},
|
|
13
13
|
"excludePaths": {
|
|
14
14
|
"type": "array",
|