@webpieces/nx-webpieces-rules 0.4.927 → 0.4.929
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 +7 -7
- package/src/configs/api-doc-rules-config.d.ts +3 -2
- package/src/configs/api-doc-rules-config.js +3 -4
- package/src/configs/api-doc-rules-config.js.map +1 -1
- package/src/configs/no-root-union-config.d.ts +3 -2
- package/src/configs/no-root-union-config.js +3 -3
- package/src/configs/no-root-union-config.js.map +1 -1
- package/src/configs/rule-configs.js +0 -10
- package/src/configs/rule-configs.js.map +1 -1
- package/src/configs/tag-truth-configs.js +0 -4
- package/src/configs/tag-truth-configs.js.map +1 -1
- package/src/configs/wiring-format-config.js +0 -1
- package/src/configs/wiring-format-config.js.map +1 -1
- package/src/executors/validate-ts-in-src/executor.d.ts +5 -4
- package/src/executors/validate-ts-in-src/executor.js +15 -10
- package/src/executors/validate-ts-in-src/executor.js.map +1 -1
- package/src/executors/validate-ts-in-src/schema.json +0 -5
- package/src/lib/api-usage/wire-closure.js +3 -2
- package/src/lib/api-usage/wire-closure.js.map +1 -1
- package/src/lib/api-usage/wire-suffix-scope.d.ts +3 -3
- package/src/lib/api-usage/wire-suffix-scope.js +7 -4
- package/src/lib/api-usage/wire-suffix-scope.js.map +1 -1
- package/src/pack-build-runner.js +1 -1
- package/src/pack-build-runner.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.929",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/api-doc-model": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/api-doc-model": "0.4.929",
|
|
24
|
+
"@webpieces/core-util": "0.4.929",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.929",
|
|
26
|
+
"@webpieces/rules-config": "0.4.929",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.929"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@nx/devkit": ">=18.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@webpieces/tooling-testkit": "0.4.
|
|
44
|
+
"@webpieces/tooling-testkit": "0.4.929"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -27,8 +27,9 @@ export type ApiDocMode = typeof API_DOC_MODES[number];
|
|
|
27
27
|
* construction: a contract that passes is one where adding `@ApiType(...)` then generates. A second
|
|
28
28
|
* implementation would drift from the generator's on the first release that improved either.
|
|
29
29
|
*
|
|
30
|
-
* `allowedPaths`
|
|
31
|
-
*
|
|
30
|
+
* `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;
|
|
31
|
+
* not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this
|
|
32
|
+
* project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is
|
|
32
33
|
* `// webpieces-disable api-rules-for-openapi -- <reason>`, and the REASON is mandatory.
|
|
33
34
|
*
|
|
34
35
|
* An EXISTING `no-any-unknown` disable deliberately does not silence this rule. That rule asks
|
|
@@ -29,8 +29,9 @@ exports.API_DOC_MODES = ['OFF', 'AFFECTED_PROJECT', 'RUN_EVERY_TIME'];
|
|
|
29
29
|
* construction: a contract that passes is one where adding `@ApiType(...)` then generates. A second
|
|
30
30
|
* implementation would drift from the generator's on the first release that improved either.
|
|
31
31
|
*
|
|
32
|
-
* `allowedPaths`
|
|
33
|
-
*
|
|
32
|
+
* `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;
|
|
33
|
+
* not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this
|
|
34
|
+
* project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is
|
|
34
35
|
* `// webpieces-disable api-rules-for-openapi -- <reason>`, and the REASON is mandatory.
|
|
35
36
|
*
|
|
36
37
|
* An EXISTING `no-any-unknown` disable deliberately does not silence this rule. That rule asks
|
|
@@ -42,7 +43,6 @@ class ApiRulesForOpenApiConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
42
43
|
static SCHEMA = {
|
|
43
44
|
mode: new rules_sdk_1.FieldDef('string', exports.API_DOC_MODES),
|
|
44
45
|
allowedPaths: rules_sdk_1.FieldDef.optional('string[]'),
|
|
45
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
exports.ApiRulesForOpenApiConfig = ApiRulesForOpenApiConfig;
|
|
@@ -63,7 +63,6 @@ class ApiRulesForMcpConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
63
63
|
static SCHEMA = {
|
|
64
64
|
mode: new rules_sdk_1.FieldDef('string', exports.API_DOC_MODES),
|
|
65
65
|
allowedPaths: rules_sdk_1.FieldDef.optional('string[]'),
|
|
66
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
exports.ApiRulesForMcpConfig = ApiRulesForMcpConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-doc-rules-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/api-doc-rules-config.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"api-doc-rules-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/api-doc-rules-config.ts"],"names":[],"mappings":";;;AAAA,oDAA6E;AAC7E;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,aAAa,GAAG,CAAC,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,CAAU,CAAC;AAIpF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,wBAAyB,SAAQ,0BAAc;IAExD,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAA0C;QAC5D,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,qBAAa,CAAC;QAC3C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;KAC9C,CAAC;;AAPN,4DAQC;AAED;;;;;;;;;;;GAWG;AACH,MAAa,oBAAqB,SAAQ,0BAAc;IAEpD,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAAsC;QACxD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,qBAAa,CAAC;QAC3C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;KAC9C,CAAC;;AAPN,oDAQC","sourcesContent":["import { FieldDef, SchemaShape, BaseRuleConfig } from \"@webpieces/rules-sdk\";\n/**\n * The modes the two CONTRACT rules accept (#1017).\n *\n * `AFFECTED_PROJECT` is the one a consumer normally picks, and it is the granularity nx already\n * gives: scan the `@ApiPath` contracts of the projects the diff actually touches, instead of every\n * contract in the workspace every time any project is affected. A contract in a project no changed\n * file belongs to cannot have changed, so scanning it is work with no verdict in it — and on a repo\n * measured at 98 endpoints that is the difference between a rule somebody leaves on and one they\n * turn off.\n *\n * `RUN_EVERY_TIME` stays, for the full-repo migration sweep: when a team decides to publish an API\n * they want the whole list at once, not the part of it they happened to edit this week.\n *\n * This is deliberately NOT diff-HUNK scoping. #1016 established that a contract's publishability is\n * not a property of the lines you touched — adding one field to a DTO can break a document through a\n * type three files away — so the unit stays the PROJECT, which is the unit nx itself works in.\n */\nexport const API_DOC_MODES = ['OFF', 'AFFECTED_PROJECT', 'RUN_EVERY_TIME'] as const;\n\nexport type ApiDocMode = typeof API_DOC_MODES[number];\n\n/**\n * `api-rules-for-openapi` — everything that must hold for an `@ApiPath` contract to PRODUCE an\n * OpenAPI document, checked on every contract in the workspace whether or not it declares `@ApiType`.\n *\n * The rule does not restate \"what is expressible\". It DRIVES `@webpieces/api-doc-model`'s own\n * extractor, so there is exactly one definition of it and the acceptance contract holds by\n * construction: a contract that passes is one where adding `@ApiType(...)` then generates. A second\n * implementation would drift from the generator's on the first release that improved either.\n *\n * `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;\n * not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this\n * project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is\n * `// webpieces-disable api-rules-for-openapi -- <reason>`, and the REASON is mandatory.\n *\n * An EXISTING `no-any-unknown` disable deliberately does not silence this rule. That rule asks\n * \"is this type-safe?\"; this one asks \"is this field PUBLISHED to a partner with no shape?\" — a\n * different question with different right answers, so it wants its own, separately argued line.\n */\nexport class ApiRulesForOpenApiConfig extends BaseRuleConfig {\n declare mode?: ApiDocMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<ApiRulesForOpenApiConfig> = {\n mode: new FieldDef('string', API_DOC_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n };\n}\n\n/**\n * `api-rules-for-mcp` — everything `api-rules-for-openapi` checks, PLUS what `McpToolRegistry`\n * enforces at boot, moved to CI.\n *\n * Two rules and not one because the blast radii differ. An OpenAPI defect is DOCUMENT-WIDE: one\n * unshaped field blocks client generation for every operation of that API. An MCP defect blocks one\n * tool. A team publishing a partner API and no tools must be able to run the first without the\n * second, and a config key per rule is the only way to say that.\n *\n * The MCP half drives `McpSchemaRenderer` itself, tool by tool, so \"this passes\" and \"this renders\"\n * are the same statement rather than two that can disagree.\n */\nexport class ApiRulesForMcpConfig extends BaseRuleConfig {\n declare mode?: ApiDocMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<ApiRulesForMcpConfig> = {\n mode: new FieldDef('string', API_DOC_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n };\n}\n"]}
|
|
@@ -13,8 +13,9 @@ import { StructuralMode } from "./rule-configs";
|
|
|
13
13
|
* already opted in would let a team discover six months afterwards that the type was never
|
|
14
14
|
* expressible — by which time it is in partners' generated clients and cannot be changed.
|
|
15
15
|
*
|
|
16
|
-
* `allowedPaths`
|
|
17
|
-
*
|
|
16
|
+
* `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;
|
|
17
|
+
* not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this
|
|
18
|
+
* project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is `// webpieces-disable no-root-union-api-type -- <reason>`, and
|
|
18
19
|
* the REASON is mandatory: this defect's blast radius is every tool in a session, so a suppression
|
|
19
20
|
* has to carry an argument somebody wrote down.
|
|
20
21
|
*/
|
|
@@ -16,8 +16,9 @@ const rule_configs_1 = require("./rule-configs");
|
|
|
16
16
|
* already opted in would let a team discover six months afterwards that the type was never
|
|
17
17
|
* expressible — by which time it is in partners' generated clients and cannot be changed.
|
|
18
18
|
*
|
|
19
|
-
* `allowedPaths`
|
|
20
|
-
*
|
|
19
|
+
* `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;
|
|
20
|
+
* not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this
|
|
21
|
+
* project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is `// webpieces-disable no-root-union-api-type -- <reason>`, and
|
|
21
22
|
* the REASON is mandatory: this defect's blast radius is every tool in a session, so a suppression
|
|
22
23
|
* has to carry an argument somebody wrote down.
|
|
23
24
|
*/
|
|
@@ -26,7 +27,6 @@ class NoRootUnionApiTypeConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
26
27
|
static SCHEMA = {
|
|
27
28
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
28
29
|
allowedPaths: rules_sdk_1.FieldDef.optional('string[]'),
|
|
29
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
exports.NoRootUnionApiTypeConfig = NoRootUnionApiTypeConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-root-union-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/no-root-union-config.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"no-root-union-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/no-root-union-config.ts"],"names":[],"mappings":";;;AAAA,oDAA6E;AAC7E,iDAAkE;AAClE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,wBAAyB,SAAQ,0BAAc;IAExD,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAA0C;QAC5D,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;KAC9C,CAAC;;AAPN,4DAQC","sourcesContent":["import { FieldDef, SchemaShape, BaseRuleConfig } from \"@webpieces/rules-sdk\";\nimport { StructuralMode, STRUCTURAL_MODES } from \"./rule-configs\";\n/**\n * `no-root-union-api-type` — a request or response type that IS a union, on any `@ApiPath` contract.\n *\n * Both the OpenAI and the Anthropic function-calling APIs forbid `oneOf`/`anyOf`/`allOf` at the TOP\n * level of a tool's parameter schema, and a server sends its WHOLE tool list on every request — so\n * ONE offending tool makes every request 400 and the entire client session is unusable, not just\n * that tool. Nested composition, inside a property, is fine; only the root is the problem.\n *\n * It is checked on EVERY `@ApiPath` contract, whether or not it declares `@ApiType`. `@ApiType` is a\n * PUBLISHING decision added later, on purpose, so a shape rule that only ran on contracts which had\n * already opted in would let a team discover six months afterwards that the type was never\n * expressible — by which time it is in partners' generated clients and cannot be changed.\n *\n * `allowedPaths` — globs over PROJECT ROOTS this rule skips (a whole project, matched against its root;\n * not a file list). The framework-owned `excludePaths` every rule accepts (#1253) is NOT applied by this\n * project scan yet — use `allowedPaths` to exempt a project (follow-up #1278). The per-site escape hatch is `// webpieces-disable no-root-union-api-type -- <reason>`, and\n * the REASON is mandatory: this defect's blast radius is every tool in a session, so a suppression\n * has to carry an argument somebody wrote down.\n */\nexport class NoRootUnionApiTypeConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoRootUnionApiTypeConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n };\n}\n"]}
|
|
@@ -30,7 +30,6 @@ class NoFileImportCyclesConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
30
30
|
ignoreTypeOnly: rules_sdk_2.FieldDef.optional('boolean'),
|
|
31
31
|
excludePackages: rules_sdk_2.FieldDef.optional('string[]'),
|
|
32
32
|
excludeRegExp: rules_sdk_2.FieldDef.optional('string[]'),
|
|
33
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
34
33
|
};
|
|
35
34
|
}
|
|
36
35
|
exports.NoFileImportCyclesConfig = NoFileImportCyclesConfig;
|
|
@@ -70,28 +69,24 @@ class RuntimeArchitectureConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
70
69
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
71
70
|
showExternalNodes: rules_sdk_2.FieldDef.optional('boolean'),
|
|
72
71
|
externalApiPaths: rules_sdk_2.FieldDef.optional('string[]'),
|
|
73
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
74
72
|
};
|
|
75
73
|
}
|
|
76
74
|
exports.RuntimeArchitectureConfig = RuntimeArchitectureConfig;
|
|
77
75
|
class NxWiringConfig extends rules_sdk_1.BaseRuleConfig {
|
|
78
76
|
static SCHEMA = {
|
|
79
77
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
80
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
81
78
|
};
|
|
82
79
|
}
|
|
83
80
|
exports.NxWiringConfig = NxWiringConfig;
|
|
84
81
|
class DiGraphConfig extends rules_sdk_1.BaseRuleConfig {
|
|
85
82
|
static SCHEMA = {
|
|
86
83
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
87
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
88
84
|
};
|
|
89
85
|
}
|
|
90
86
|
exports.DiGraphConfig = DiGraphConfig;
|
|
91
87
|
class MissingDesignAnnotationConfig extends rules_sdk_1.BaseRuleConfig {
|
|
92
88
|
static SCHEMA = {
|
|
93
89
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
94
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
95
90
|
};
|
|
96
91
|
}
|
|
97
92
|
exports.MissingDesignAnnotationConfig = MissingDesignAnnotationConfig;
|
|
@@ -112,35 +107,30 @@ exports.MissingDesignAnnotationConfig = MissingDesignAnnotationConfig;
|
|
|
112
107
|
class ValidateArchitectureUnchangedConfig extends rules_sdk_1.BaseRuleConfig {
|
|
113
108
|
static SCHEMA = {
|
|
114
109
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
115
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
116
110
|
};
|
|
117
111
|
}
|
|
118
112
|
exports.ValidateArchitectureUnchangedConfig = ValidateArchitectureUnchangedConfig;
|
|
119
113
|
class ValidateNoArchitectureCyclesConfig extends rules_sdk_1.BaseRuleConfig {
|
|
120
114
|
static SCHEMA = {
|
|
121
115
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
122
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
123
116
|
};
|
|
124
117
|
}
|
|
125
118
|
exports.ValidateNoArchitectureCyclesConfig = ValidateNoArchitectureCyclesConfig;
|
|
126
119
|
class ValidatePackageJsonConfig extends rules_sdk_1.BaseRuleConfig {
|
|
127
120
|
static SCHEMA = {
|
|
128
121
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
129
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
130
122
|
};
|
|
131
123
|
}
|
|
132
124
|
exports.ValidatePackageJsonConfig = ValidatePackageJsonConfig;
|
|
133
125
|
class ValidateVersionsLockedConfig extends rules_sdk_1.BaseRuleConfig {
|
|
134
126
|
static SCHEMA = {
|
|
135
127
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
136
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
137
128
|
};
|
|
138
129
|
}
|
|
139
130
|
exports.ValidateVersionsLockedConfig = ValidateVersionsLockedConfig;
|
|
140
131
|
class ValidateEslintSyncConfig extends rules_sdk_1.BaseRuleConfig {
|
|
141
132
|
static SCHEMA = {
|
|
142
133
|
mode: new rules_sdk_2.FieldDef('string', exports.STRUCTURAL_MODES),
|
|
143
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
144
134
|
};
|
|
145
135
|
}
|
|
146
136
|
exports.ValidateEslintSyncConfig = ValidateEslintSyncConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-configs.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/rule-configs.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig, BASE_RULE_SCHEMA } from \"@webpieces/rules-sdk\";\nimport { FieldDef, SchemaShape } from \"@webpieces/rules-sdk\";\n// Structural / whole-graph rules (import-cycle, runtime-architecture, nx-wiring). They can't be\n// scoped to changed lines/files — a cycle or wiring break can route through a project that wasn't\n// itself edited — so when active they run the FULL check every time (nx-affected already limits\n// them to affected projects externally). RUN_EVERY_TIME replaces the old, vaguer \"ON\".\nexport const STRUCTURAL_MODES = ['OFF', 'RUN_EVERY_TIME'] as const;\n\nexport type StructuralMode = typeof STRUCTURAL_MODES[number];\n\n// NOTE: there is deliberately no `WholeRepoBuildGuardConfig`. That guard has NO webpieces.config.json\n// entry — it is EXPERIMENTAL and OFF by default, and its only switch is the OPT-IN\n// `experimental.whole-repo-build-guard: true` in the optional machine-local ~/.webpieces/config.json\n// (see home-config.ts). The build command it prints\n// is handed to it directly from `commands.pr-gate.buildCommand` by ai-hook-rules' runner. Re-adding a\n// config class here puts the guard back in RULE_SCHEMAS and makes it a fault-Y rule again.\n\nexport class NoFileImportCyclesConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n ignoreTypeOnly?: boolean;\n excludePackages?: string[];\n // Raw regex escape hatch for a cycle inside the project being checked — generated code, a\n // vendored tree, or a deliberate bidirectional domain model — that `excludePackages` cannot reach\n // (it resolves npm package NAMES, so it only excludes a *sibling* package, never a directory\n // inside this one). Patterns are handed to madge verbatim and matched against ids RELATIVE TO THE\n // PROJECT (e.g. \"^src/generated/\", \"^src/modules/(item|category)/\") — NOT workspace-rooted or\n // absolute, which silently match nothing. The executor warns when a pattern matches zero\n // traversed files, so a mis-anchored pattern is visible rather than a silent no-op.\n excludeRegExp?: string[];\n\n static readonly SCHEMA: SchemaShape<NoFileImportCyclesConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ignoreTypeOnly: FieldDef.optional('boolean'),\n excludePackages: FieldDef.optional('string[]'),\n excludeRegExp: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class RuntimeArchitectureConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n // NOTE: `servicePaths` / `apiProjectPaths` were removed — they were never read. The runtime graph\n // is derived SOLELY from architecture/dependencies.json apiRelations + project roles (see\n // nx-webpieces-rules/src/lib/runtime-graph.ts), so any config still listing them (a hand-maintained\n // enumeration of api libs) now fails the unknown-field check in validateWebpiecesConfig. There is\n // nothing to enumerate — delete the keys.\n // NOTE: `allowedCycles` was removed — a runtime cycle is no longer allowable at all. CD deploys\n // services in dependency order and a cyclic architecture has no such order, so the runtime graph\n // now THROWS while it is being levelled rather than emitting a flat, unlevellable diagram (see\n // nx-webpieces-rules/src/lib/runtime-graph-levels.ts). A repo that genuinely cannot break a cycle\n // yet declares it PER EDGE with a `cutLegacyCycle:<targetService>` nx tag on the CALLING project,\n // which admits the debt in a place `grep -rn cutLegacyCycle` can enumerate. Delete the key.\n /**\n * Draw a dashed terminal node for every contract a service calls that NOTHING in-repo\n * implements (firestore, gmail, ...) — the vendor systems the runtime graph otherwise stops one\n * hop short of. Defaults to true; set false in a repo whose external surface is noisy. Purely a\n * RENDER switch: levels, cycle detection and runtime-dependencies.json are identical either way.\n */\n showExternalNodes?: boolean;\n /**\n * Project roots whose exported `*Api` types are contracts for systems OUTSIDE this repo\n * (firestore, gmail, gcp-storage, ...), e.g. `[\"libraries/apis/external/**\"]`. Globs, matched\n * against the nx project root.\n *\n * Needed because an external contract does NOT look like an in-repo one: it is a plain\n * `interface` bound to a Symbol token and injected, never an `abstract class` carrying @ApiPath\n * reached through `createRpcClient`. Without this list the scanner has no way to tell a vendor\n * seam from any other library, so every call leaving the repo is invisible to the runtime graph.\n *\n * Defaults to none, which is correct for a repo with no vendor wrapper libraries.\n */\n externalApiPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<RuntimeArchitectureConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n showExternalNodes: FieldDef.optional('boolean'),\n externalApiPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NxWiringConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<NxWiringConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class DiGraphConfig extends BaseRuleConfig {\n // Structural: the DI graph is regenerated whole-project on every build (generate +\n // unchanged gate), so it cannot be scoped to changed lines.\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<DiGraphConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class MissingDesignAnnotationConfig extends BaseRuleConfig {\n // Structural: enforced per-project by the di-graph-generate executor, which\n // roots the whole DI design on @DocumentDesign classes — cannot be line-scoped.\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<MissingDesignAnnotationConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The five Nx infrastructure validators (architecture-unchanged, no-architecture-cycles,\n// packagejson, versions-locked, eslint-sync). Each is whole-graph / whole-repo by nature (a cycle, a\n// drifted dependencies.json, an unlocked version can be introduced by a file nobody in this diff\n// touched), so the only honest mode set is STRUCTURAL_MODES: RUN_EVERY_TIME (the default) or OFF.\n//\n// All five now honor the universal escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch)\n// via shouldSkipRule — the RuleGate is called with honorEpoch:true from\n// every executor. This lets a repo time-box or branch-scope a failing infrastructure check (e.g. hold\n// validate-packagejson off until an upgrade PR lands) with a one-value edit, instead of only the\n// blunt \"mode\": \"OFF\". Originally packagejson/versions-locked/eslint-sync were all-or-nothing on the\n// theory that \"no blessed baseline\" made grandfathering meaningless, but a time-box is a schedule, not\n// a baseline: \"do not enforce this until <epoch>/off <branch>\" is coherent for any rule.\n// ---------------------------------------------------------------------------\n\nexport class ValidateArchitectureUnchangedConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateArchitectureUnchangedConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateNoArchitectureCyclesConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateNoArchitectureCyclesConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidatePackageJsonConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidatePackageJsonConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateVersionsLockedConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateVersionsLockedConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateEslintSyncConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateEslintSyncConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rule-configs.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/rule-configs.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig } from \"@webpieces/rules-sdk\";\nimport { FieldDef, SchemaShape } from \"@webpieces/rules-sdk\";\n// Structural / whole-graph rules (import-cycle, runtime-architecture, nx-wiring). They can't be\n// scoped to changed lines/files — a cycle or wiring break can route through a project that wasn't\n// itself edited — so when active they run the FULL check every time (nx-affected already limits\n// them to affected projects externally). RUN_EVERY_TIME replaces the old, vaguer \"ON\".\nexport const STRUCTURAL_MODES = ['OFF', 'RUN_EVERY_TIME'] as const;\n\nexport type StructuralMode = typeof STRUCTURAL_MODES[number];\n\n// NOTE: there is deliberately no `WholeRepoBuildGuardConfig`. That guard has NO webpieces.config.json\n// entry — it is EXPERIMENTAL and OFF by default, and its only switch is the OPT-IN\n// `experimental.whole-repo-build-guard: true` in the optional machine-local ~/.webpieces/config.json\n// (see home-config.ts). The build command it prints\n// is handed to it directly from `commands.pr-gate.buildCommand` by ai-hook-rules' runner. Re-adding a\n// config class here puts the guard back in RULE_SCHEMAS and makes it a fault-Y rule again.\n\nexport class NoFileImportCyclesConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n ignoreTypeOnly?: boolean;\n excludePackages?: string[];\n // Raw regex escape hatch for a cycle inside the project being checked — generated code, a\n // vendored tree, or a deliberate bidirectional domain model — that `excludePackages` cannot reach\n // (it resolves npm package NAMES, so it only excludes a *sibling* package, never a directory\n // inside this one). Patterns are handed to madge verbatim and matched against ids RELATIVE TO THE\n // PROJECT (e.g. \"^src/generated/\", \"^src/modules/(item|category)/\") — NOT workspace-rooted or\n // absolute, which silently match nothing. The executor warns when a pattern matches zero\n // traversed files, so a mis-anchored pattern is visible rather than a silent no-op.\n excludeRegExp?: string[];\n\n static readonly SCHEMA: SchemaShape<NoFileImportCyclesConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n ignoreTypeOnly: FieldDef.optional('boolean'),\n excludePackages: FieldDef.optional('string[]'),\n excludeRegExp: FieldDef.optional('string[]'),\n };\n}\n\nexport class RuntimeArchitectureConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n // NOTE: `servicePaths` / `apiProjectPaths` were removed — they were never read. The runtime graph\n // is derived SOLELY from architecture/dependencies.json apiRelations + project roles (see\n // nx-webpieces-rules/src/lib/runtime-graph.ts), so any config still listing them (a hand-maintained\n // enumeration of api libs) now fails the unknown-field check in validateWebpiecesConfig. There is\n // nothing to enumerate — delete the keys.\n // NOTE: `allowedCycles` was removed — a runtime cycle is no longer allowable at all. CD deploys\n // services in dependency order and a cyclic architecture has no such order, so the runtime graph\n // now THROWS while it is being levelled rather than emitting a flat, unlevellable diagram (see\n // nx-webpieces-rules/src/lib/runtime-graph-levels.ts). A repo that genuinely cannot break a cycle\n // yet declares it PER EDGE with a `cutLegacyCycle:<targetService>` nx tag on the CALLING project,\n // which admits the debt in a place `grep -rn cutLegacyCycle` can enumerate. Delete the key.\n /**\n * Draw a dashed terminal node for every contract a service calls that NOTHING in-repo\n * implements (firestore, gmail, ...) — the vendor systems the runtime graph otherwise stops one\n * hop short of. Defaults to true; set false in a repo whose external surface is noisy. Purely a\n * RENDER switch: levels, cycle detection and runtime-dependencies.json are identical either way.\n */\n showExternalNodes?: boolean;\n /**\n * Project roots whose exported `*Api` types are contracts for systems OUTSIDE this repo\n * (firestore, gmail, gcp-storage, ...), e.g. `[\"libraries/apis/external/**\"]`. Globs, matched\n * against the nx project root.\n *\n * Needed because an external contract does NOT look like an in-repo one: it is a plain\n * `interface` bound to a Symbol token and injected, never an `abstract class` carrying @ApiPath\n * reached through `createRpcClient`. Without this list the scanner has no way to tell a vendor\n * seam from any other library, so every call leaving the repo is invisible to the runtime graph.\n *\n * Defaults to none, which is correct for a repo with no vendor wrapper libraries.\n */\n externalApiPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<RuntimeArchitectureConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n showExternalNodes: FieldDef.optional('boolean'),\n externalApiPaths: FieldDef.optional('string[]'),\n };\n}\n\nexport class NxWiringConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<NxWiringConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class DiGraphConfig extends BaseRuleConfig {\n // Structural: the DI graph is regenerated whole-project on every build (generate +\n // unchanged gate), so it cannot be scoped to changed lines.\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<DiGraphConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class MissingDesignAnnotationConfig extends BaseRuleConfig {\n // Structural: enforced per-project by the di-graph-generate executor, which\n // roots the whole DI design on @DocumentDesign classes — cannot be line-scoped.\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<MissingDesignAnnotationConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The five Nx infrastructure validators (architecture-unchanged, no-architecture-cycles,\n// packagejson, versions-locked, eslint-sync). Each is whole-graph / whole-repo by nature (a cycle, a\n// drifted dependencies.json, an unlocked version can be introduced by a file nobody in this diff\n// touched), so the only honest mode set is STRUCTURAL_MODES: RUN_EVERY_TIME (the default) or OFF.\n//\n// All five now honor the universal escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch)\n// via shouldSkipRule — the RuleGate is called with honorEpoch:true from\n// every executor. This lets a repo time-box or branch-scope a failing infrastructure check (e.g. hold\n// validate-packagejson off until an upgrade PR lands) with a one-value edit, instead of only the\n// blunt \"mode\": \"OFF\". Originally packagejson/versions-locked/eslint-sync were all-or-nothing on the\n// theory that \"no blessed baseline\" made grandfathering meaningless, but a time-box is a schedule, not\n// a baseline: \"do not enforce this until <epoch>/off <branch>\" is coherent for any rule.\n// ---------------------------------------------------------------------------\n\nexport class ValidateArchitectureUnchangedConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateArchitectureUnchangedConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class ValidateNoArchitectureCyclesConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateNoArchitectureCyclesConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class ValidatePackageJsonConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidatePackageJsonConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class ValidateVersionsLockedConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateVersionsLockedConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n\nexport class ValidateEslintSyncConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n\n static readonly SCHEMA: SchemaShape<ValidateEslintSyncConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n };\n}\n"]}
|
|
@@ -34,7 +34,6 @@ class ApiLibDependenciesConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
34
34
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
35
35
|
apiLibPackages: new rules_sdk_1.FieldDef('string[]'),
|
|
36
36
|
apiClients: rules_sdk_1.FieldDef.objects(ApiClientPackagesEntry.SCHEMA),
|
|
37
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
38
37
|
};
|
|
39
38
|
}
|
|
40
39
|
exports.ApiLibDependenciesConfig = ApiLibDependenciesConfig;
|
|
@@ -50,7 +49,6 @@ class ApiLibPathConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
50
49
|
static SCHEMA = {
|
|
51
50
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
52
51
|
paths: rules_sdk_1.FieldDef.nonEmptyStrings(),
|
|
53
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
54
|
exports.ApiLibPathConfig = ApiLibPathConfig;
|
|
@@ -91,7 +89,6 @@ class FrameworkFolderConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
91
89
|
static SCHEMA = {
|
|
92
90
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
93
91
|
entries: rules_sdk_1.FieldDef.nonEmptyObjects(FrameworkFolderEntry.SCHEMA),
|
|
94
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
95
92
|
};
|
|
96
93
|
}
|
|
97
94
|
exports.FrameworkFolderConfig = FrameworkFolderConfig;
|
|
@@ -141,7 +138,6 @@ class ProductTagsConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
141
138
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
142
139
|
roles: rules_sdk_1.FieldDef.nonEmptyStrings(),
|
|
143
140
|
productDependencies: rules_sdk_1.FieldDef.objects(ProductDependencyEntry.SCHEMA),
|
|
144
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
145
141
|
};
|
|
146
142
|
}
|
|
147
143
|
exports.ProductTagsConfig = ProductTagsConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag-truth-configs.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/tag-truth-configs.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"tag-truth-configs.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/tag-truth-configs.ts"],"names":[],"mappings":";;;AAAA,oDAA6E;AAC7E,iDAAkE;AAClE,+GAA+G;AAC/G,MAAa,sBAAsB;IAC/B,iFAAiF;IACjF,OAAO,CAAU;IACjB,mGAAmG;IACnG,QAAQ,CAAY;IAEpB,MAAM,CAAU,MAAM,GAAwC;QAC1D,OAAO,EAAE,IAAI,oBAAQ,CAAC,QAAQ,CAAC;QAC/B,QAAQ,EAAE,IAAI,oBAAQ,CAAC,UAAU,CAAC;KACrC,CAAC;;AATN,wDAUC;AAED;;;;;;;;;;;GAWG;AACH,MAAa,wBAAyB,SAAQ,0BAAc;IAExD,cAAc,CAAY;IAC1B,UAAU,CAA4B;IAEtC,MAAM,CAAU,MAAM,GAA0C;QAC5D,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,cAAc,EAAE,IAAI,oBAAQ,CAAC,UAAU,CAAC;QACxC,UAAU,EAAE,oBAAQ,CAAC,OAAO,CAAC,sBAAsB,CAAC,MAAM,CAAC;KAC9D,CAAC;;AATN,4DAUC;AAED;;;;;;GAMG;AACH,MAAa,gBAAiB,SAAQ,0BAAc;IAEhD,KAAK,CAAY;IAEjB,MAAM,CAAU,MAAM,GAAkC;QACpD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,KAAK,EAAE,oBAAQ,CAAC,eAAe,EAAE;KACpC,CAAC;;AAPN,4CAQC;AAED;;;;;;GAMG;AACH,MAAa,oBAAoB;IAC7B,8EAA8E;IAC9E,KAAK,CAAY;IACjB,qGAAqG;IACrG,aAAa,CAAY;IACzB,qEAAqE;IACrE,KAAK,CAAY;IAEjB,MAAM,CAAU,MAAM,GAAsC;QACxD,KAAK,EAAE,oBAAQ,CAAC,eAAe,EAAE;QACjC,aAAa,EAAE,oBAAQ,CAAC,eAAe,EAAE;QACzC,KAAK,EAAE,oBAAQ,CAAC,eAAe,EAAE;KACpC,CAAC;;AAZN,oDAaC;AAED;;;;;;;;;;GAUG;AACH,MAAa,qBAAsB,SAAQ,0BAAc;IAErD,OAAO,CAA0B;IAEjC,MAAM,CAAU,MAAM,GAAuC;QACzD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,OAAO,EAAE,oBAAQ,CAAC,eAAe,CAAC,oBAAoB,CAAC,MAAM,CAAC;KACjE,CAAC;;AAPN,sDAQC;AAED;;;GAGG;AACH,MAAa,sBAAsB;IAC/B,wGAAwG;IACxG,IAAI,CAAU;IACd;;;OAGG;IACH,iBAAiB,CAAY;IAE7B,MAAM,CAAU,MAAM,GAAwC;QAC1D,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,CAAC;QAC5B,iBAAiB,EAAE,IAAI,oBAAQ,CAAC,UAAU,CAAC;KAC9C,CAAC;;AAZN,wDAaC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,iBAAkB,SAAQ,0BAAc;IAEjD,KAAK,CAAY;IACjB,mBAAmB,CAA4B;IAE/C,MAAM,CAAU,MAAM,GAAmC;QACrD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,KAAK,EAAE,oBAAQ,CAAC,eAAe,EAAE;QACjC,mBAAmB,EAAE,oBAAQ,CAAC,OAAO,CAAC,sBAAsB,CAAC,MAAM,CAAC;KACvE,CAAC;;AATN,8CAUC","sourcesContent":["import { FieldDef, SchemaShape, BaseRuleConfig } from \"@webpieces/rules-sdk\";\nimport { StructuralMode, STRUCTURAL_MODES } from \"./rule-configs\";\n/** One `role:api-client` project and the outside packages it may import (its SDK, inversify, …). Data-only. */\nexport class ApiClientPackagesEntry {\n /** The nx project name of the `role:api-client` library, e.g. `gmail-client`. */\n project!: string;\n /** Package names (globs allowed, e.g. `@google-cloud/*`) it may import beyond `apiLibPackages`. */\n packages!: string[];\n\n static readonly SCHEMA: SchemaShape<ApiClientPackagesEntry> = {\n project: new FieldDef('string'),\n packages: new FieldDef('string[]'),\n };\n}\n\n/**\n * `api-lib-dependencies` (D2) — a `role:api-lib` may depend only on other `role:api-lib` projects plus\n * the outside packages in `apiLibPackages`; a `role:api-client` may ALSO import the packages its own\n * `apiClients` entry names (its SDK, `inversify`, `@webpieces/core-context`, …).\n *\n * - `mode` — OFF | RUN_EVERY_TIME (a graph rule: it reads every project, every run).\n * - `apiLibPackages` — required; may be empty (an api library then imports nothing from outside), e.g.\n * `[\"@webpieces/core-util\", \"tslib\"]`. A workspace project whose name or package name is listed here\n * is allowed too, so a repo whose `core-util` is a workspace project states it the same way.\n * - `apiClients` — required; one entry per `role:api-client` project. A `role:api-client` with no\n * entry is itself a violation: what it may talk to is stated, never inferred.\n */\nexport class ApiLibDependenciesConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n apiLibPackages!: string[];\n apiClients!: ApiClientPackagesEntry[];\n\n static readonly SCHEMA: SchemaShape<ApiLibDependenciesConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n apiLibPackages: new FieldDef('string[]'),\n apiClients: FieldDef.objects(ApiClientPackagesEntry.SCHEMA),\n };\n}\n\n/**\n * `api-lib-path` (D10) — both directions: every `role:api-lib` / `role:api-client` project lives under\n * one of `paths`, and every project under `paths` carries one of those two roles.\n *\n * - `mode` — OFF | RUN_EVERY_TIME.\n * - `paths` — required, non-empty globs over project ROOTS, e.g. `[\"libraries/apis/**\"]`.\n */\nexport class ApiLibPathConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n paths!: string[];\n\n static readonly SCHEMA: SchemaShape<ApiLibPathConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n paths: FieldDef.nonEmptyStrings(),\n };\n}\n\n/**\n * One folder of the runtime layout: the project roots it covers, the framework SETS a project there may\n * carry, and the roles it may have. Data-only.\n *\n * A framework set is written as its tags joined by `+`, in any order: `\"browser+node+react-native\"`.\n * Listing two sets means EITHER — `[\"node\", \"express\"]` is a node library or an express one.\n */\nexport class FrameworkFolderEntry {\n /** Globs over project ROOTS, e.g. `[\"libraries/universal/**\"]`. Non-empty. */\n paths!: string[];\n /** The accepted framework sets, each `+`-joined, e.g. `[\"browser+node+react-native\"]`. Non-empty. */\n frameworkSets!: string[];\n /** The accepted roles, e.g. `[\"lib\", \"designed-lib\"]`. Non-empty. */\n roles!: string[];\n\n static readonly SCHEMA: SchemaShape<FrameworkFolderEntry> = {\n paths: FieldDef.nonEmptyStrings(),\n frameworkSets: FieldDef.nonEmptyStrings(),\n roles: FieldDef.nonEmptyStrings(),\n };\n}\n\n/**\n * `framework-folder` (D9) — the folder a library lives in and the tags it carries say the same thing,\n * both directions:\n * - a project under an entry's `paths` (the FIRST entry whose glob matches, in `entries` order) must\n * carry one of that entry's `frameworkSets`, exactly, and one of its `roles`;\n * - a LIBRARY (any role but server/app/client/bundle) carrying a framework set and role that some entry\n * lists must live under one of the entries that list them.\n *\n * - `mode` — OFF | RUN_EVERY_TIME.\n * - `entries` — required, non-empty. The recommended layout is in `.claude/rules/framework-tags.md`.\n */\nexport class FrameworkFolderConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n entries!: FrameworkFolderEntry[];\n\n static readonly SCHEMA: SchemaShape<FrameworkFolderConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n entries: FieldDef.nonEmptyObjects(FrameworkFolderEntry.SCHEMA),\n };\n}\n\n/**\n * One row of the product dependency matrix (#1204): the OTHER products a product may depend on.\n * Data-only.\n */\nexport class ProductDependencyEntry {\n /** The product this row is about — the name after `product:` in some project's tags, e.g. `example`. */\n name!: string;\n /**\n * The other products a project of `name` may depend on, e.g. `[\"platform\", \"openapi\"]`. A product\n * may always depend on itself, so it is never listed; `[]` states that it depends on no other.\n */\n allowedToDependOn!: string[];\n\n static readonly SCHEMA: SchemaShape<ProductDependencyEntry> = {\n name: new FieldDef('string'),\n allowedToDependOn: new FieldDef('string[]'),\n };\n}\n\n/**\n * `product-tags` (#1179, #1204) — two halves behind ONE switch:\n * - every project whose role is in `roles` carries at least one well-formed `product:<name>` nx tag\n * (lowercase kebab, several allowed). The product filter and the Product color mode of\n * `arch:visualize` / `arch:visualize-runtime` seed a product's membership from exactly those tags,\n * so an untagged server, client or app silently drops out of every product view;\n * - every edge of the DEPENDENCY graph respects `productDependencies`: an edge A -> B is legal when,\n * for EVERY product p of A, B carries p or a product p's entry allows. Membership is the resolved\n * one (a project's own tags are final; an untagged project inherits). The runtime graph is not\n * checked.\n *\n * - `mode` — OFF | RUN_EVERY_TIME (a graph rule: it reads every project, every run). OFF turns off\n * both halves.\n * - `roles` — required, non-empty: WHICH roles are refused without a tag, e.g.\n * `[\"server\", \"client\", \"app\"]`. It decides which projects fail, so it is behaviour, never a default.\n * - `productDependencies` — required; `[]` is valid and means no product may depend on another. One\n * entry per product that DOES depend on others, e.g.\n * `[{ \"name\": \"example\", \"allowedToDependOn\": [\"platform\", \"openapi\"] }]`. A product with no entry\n * may depend only on itself. A `name` or `allowedToDependOn` value that no `product:` tag uses, and a\n * duplicate `name`, fail generation naming it — a typo can never silently allow or forbid nothing.\n */\nexport class ProductTagsConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n roles!: string[];\n productDependencies!: ProductDependencyEntry[];\n\n static readonly SCHEMA: SchemaShape<ProductTagsConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n roles: FieldDef.nonEmptyStrings(),\n productDependencies: FieldDef.objects(ProductDependencyEntry.SCHEMA),\n };\n}\n"]}
|
|
@@ -7,7 +7,6 @@ class WiringFormatConfig extends rules_sdk_1.BaseRuleConfig {
|
|
|
7
7
|
static SCHEMA = {
|
|
8
8
|
mode: new rules_sdk_1.FieldDef('string', rule_configs_1.STRUCTURAL_MODES),
|
|
9
9
|
maxLines: new rules_sdk_1.FieldDef('number'),
|
|
10
|
-
...rules_sdk_1.BASE_RULE_SCHEMA,
|
|
11
10
|
};
|
|
12
11
|
}
|
|
13
12
|
exports.WiringFormatConfig = WiringFormatConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wiring-format-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/wiring-format-config.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"wiring-format-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/configs/wiring-format-config.ts"],"names":[],"mappings":";;;AAAA,oDAA6E;AAC7E,iDAAkE;AAElE,MAAa,kBAAmB,SAAQ,0BAAc;IAIlD,MAAM,CAAU,MAAM,GAAoC;QACtD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,+BAAgB,CAAC;QAC9C,QAAQ,EAAE,IAAI,oBAAQ,CAAC,QAAQ,CAAC;KACnC,CAAC;;AAPN,gDAQC","sourcesContent":["import { BaseRuleConfig, FieldDef, SchemaShape } from '@webpieces/rules-sdk';\nimport { STRUCTURAL_MODES, StructuralMode } from './rule-configs';\n\nexport class WiringFormatConfig extends BaseRuleConfig {\n declare mode?: StructuralMode;\n declare maxLines: number;\n\n static readonly SCHEMA: SchemaShape<WiringFormatConfig> = {\n mode: new FieldDef('string', STRUCTURAL_MODES),\n maxLines: new FieldDef('number'),\n };\n}\n"]}
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* Nx project. Orphan files (at workspace root or in a non-project
|
|
8
8
|
* directory) fail the rule unless explicitly allowlisted.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* A built-in floor (bare dir names + globs, e.g. **\/*.d.ts and **\/jest.config.ts) exempts files from
|
|
11
|
+
* BOTH layers. The configured `excludePaths` — this rule's and the repo's top-level one — are the
|
|
12
|
+
* framework-owned exclusions (#1253): the rule runs inside a RuleFileScope carrying them, so the changed-file
|
|
13
|
+
* listing never contains a file they match. They ADD to the floor.
|
|
12
14
|
*
|
|
13
15
|
* Only the files changed vs the base branch are validated (NEW_AND_MODIFIED_FILES),
|
|
14
16
|
* so the rule applies cleanly to legacy projects — pre-existing orphan files
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
* Configurable via webpieces.config.json:
|
|
18
20
|
* "validate-ts-in-src": {
|
|
19
21
|
* "mode": "NEW_AND_MODIFIED_FILES", // "OFF" disables the rule
|
|
20
|
-
* "excludePaths": [...], //
|
|
22
|
+
* "excludePaths": [...], // globs, e.g. "**\/codegen.ts", "scripts/**"
|
|
21
23
|
* "allowedRootFiles": [...],
|
|
22
24
|
* "turnOffRuleUntilEpoch": 0, // epoch SECONDS; skip the rule until it passes
|
|
23
25
|
* "turnOffRuleWhileOnBranch": null // branch name; skip the rule while it is checked out
|
|
@@ -29,7 +31,6 @@ import type { ExecutorContext } from '@nx/devkit';
|
|
|
29
31
|
export type ValidateTsInSrcMode = 'OFF' | 'NEW_AND_MODIFIED_FILES';
|
|
30
32
|
export interface ValidateTsInSrcOptions {
|
|
31
33
|
mode?: ValidateTsInSrcMode;
|
|
32
|
-
excludePaths?: string[];
|
|
33
34
|
allowedRootFiles?: string[];
|
|
34
35
|
turnOffRuleUntilEpoch?: number;
|
|
35
36
|
turnOffRuleWhileOnBranch?: string | null;
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* Nx project. Orphan files (at workspace root or in a non-project
|
|
9
9
|
* directory) fail the rule unless explicitly allowlisted.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* A built-in floor (bare dir names + globs, e.g. **\/*.d.ts and **\/jest.config.ts) exempts files from
|
|
12
|
+
* BOTH layers. The configured `excludePaths` — this rule's and the repo's top-level one — are the
|
|
13
|
+
* framework-owned exclusions (#1253): the rule runs inside a RuleFileScope carrying them, so the changed-file
|
|
14
|
+
* listing never contains a file they match. They ADD to the floor.
|
|
13
15
|
*
|
|
14
16
|
* Only the files changed vs the base branch are validated (NEW_AND_MODIFIED_FILES),
|
|
15
17
|
* so the rule applies cleanly to legacy projects — pre-existing orphan files
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
* Configurable via webpieces.config.json:
|
|
19
21
|
* "validate-ts-in-src": {
|
|
20
22
|
* "mode": "NEW_AND_MODIFIED_FILES", // "OFF" disables the rule
|
|
21
|
-
* "excludePaths": [...], //
|
|
23
|
+
* "excludePaths": [...], // globs, e.g. "**\/codegen.ts", "scripts/**"
|
|
22
24
|
* "allowedRootFiles": [...],
|
|
23
25
|
* "turnOffRuleUntilEpoch": 0, // epoch SECONDS; skip the rule until it passes
|
|
24
26
|
* "turnOffRuleWhileOnBranch": null // branch name; skip the rule while it is checked out
|
|
@@ -33,7 +35,7 @@ const devkit_1 = require("@nx/devkit");
|
|
|
33
35
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
34
36
|
const path = tslib_1.__importStar(require("path"));
|
|
35
37
|
const fs = tslib_1.__importStar(require("fs"));
|
|
36
|
-
const
|
|
38
|
+
const BUILT_IN_EXCLUDE_PATHS = [
|
|
37
39
|
'node_modules', 'dist', '.nx', '.git',
|
|
38
40
|
'architecture', 'tmp', 'scripts',
|
|
39
41
|
'**/*.d.ts', '**/jest.config.ts',
|
|
@@ -121,7 +123,7 @@ function reportLayerOneFailure(violations) {
|
|
|
121
123
|
}
|
|
122
124
|
console.error('\nTo fix, pick one:');
|
|
123
125
|
console.error(' (a) Move the .ts file(s) into the src/ directory');
|
|
124
|
-
console.error(' (b) Add a glob
|
|
126
|
+
console.error(' (b) Add a glob to validate-ts-in-src.excludePaths in');
|
|
125
127
|
console.error(' webpieces.config.json (e.g. "**/codegen.ts"). Defaults already');
|
|
126
128
|
console.error(' exempt **/*.d.ts and **/jest.config.ts.\n');
|
|
127
129
|
}
|
|
@@ -136,8 +138,9 @@ function reportLayerTwoFailure(violations) {
|
|
|
136
138
|
console.error('\nTo fix, pick one:');
|
|
137
139
|
console.error(' (a) Move the file into an existing project\'s src/ directory');
|
|
138
140
|
console.error(' (b) Create a new project (add project.json) that owns the directory');
|
|
139
|
-
console.error(' (c) Add the containing
|
|
140
|
-
console.error(' in
|
|
141
|
+
console.error(' (c) Add a glob for the containing directory (e.g. "tools/**") to validate-ts-in-src.excludePaths');
|
|
142
|
+
console.error(' in the validate-ts-in-src entry of webpieces.config.json (or its owner rule file), or to');
|
|
143
|
+
console.error(' the top-level excludePaths for every rule; or add the filename to allowedRootFiles');
|
|
141
144
|
console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\n');
|
|
142
145
|
}
|
|
143
146
|
async function runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles) {
|
|
@@ -190,7 +193,8 @@ async function runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFile
|
|
|
190
193
|
async function runExecutor(_nxOptions, context) {
|
|
191
194
|
// Config comes from webpieces.config.json — same source as ai-hooks
|
|
192
195
|
// and validate-code — via @webpieces/rules-config.
|
|
193
|
-
const
|
|
196
|
+
const loaded = (0, rules_config_1.loadAndValidate)(context.root);
|
|
197
|
+
const shared = loaded.resolved;
|
|
194
198
|
const rule = shared.rules.get('validate-ts-in-src');
|
|
195
199
|
const rawMode = rule?.options['mode'] ?? 'NEW_AND_MODIFIED_FILES';
|
|
196
200
|
const epoch = rule?.options['turnOffRuleUntilEpoch'];
|
|
@@ -204,10 +208,11 @@ async function runExecutor(_nxOptions, context) {
|
|
|
204
208
|
return { success: true };
|
|
205
209
|
}
|
|
206
210
|
const workspaceRoot = context.root;
|
|
207
|
-
|
|
211
|
+
// The framework-owned exclusions (#1253): the repo's top-level list plus this rule's own.
|
|
212
|
+
const exclusions = new rules_config_1.RuleFileExclusions('validate-ts-in-src', loaded.excludePaths.paths, rule?.options['excludePaths'] ?? []);
|
|
208
213
|
const allowedRootFiles = rule?.options['allowedRootFiles'] ?? DEFAULT_ALLOWED_ROOT_FILES;
|
|
209
214
|
// Only NEW_AND_MODIFIED_FILES remains once OFF is handled above — validate just the
|
|
210
215
|
// files changed vs the base branch (legacy-friendly; no whole-workspace scan).
|
|
211
|
-
return runModifiedFilesMode(workspaceRoot,
|
|
216
|
+
return new rules_config_1.RuleFileScope().runWithin(exclusions, () => runModifiedFilesMode(workspaceRoot, BUILT_IN_EXCLUDE_PATHS, allowedRootFiles));
|
|
212
217
|
}
|
|
213
218
|
//# sourceMappingURL=executor.js.map
|
|
@@ -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 (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 * \"turnOffRuleUntilEpoch\": 0, // epoch SECONDS; skip the rule until it passes\n * \"turnOffRuleWhileOnBranch\": null // branch name; skip the rule while it is checked out\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';\nimport * as fs from 'fs';\n\nexport type ValidateTsInSrcMode = 'OFF' | 'NEW_AND_MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n excludePaths?: string[];\n allowedRootFiles?: string[];\n turnOffRuleUntilEpoch?: number;\n turnOffRuleWhileOnBranch?: string | null;\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 // git diff --name-only lists DELETED paths too (renames = delete+add);\n // a file that no longer exists cannot be an orphan - skip it\n const changedFiles = getChangedFiles(workspaceRoot, base, head)\n .filter((relPath: string) => fs.existsSync(path.join(workspaceRoot, relPath)));\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['turnOffRuleUntilEpoch'] as number | undefined;\n // turnOffRuleWhileOnBranch is required-but-NULLABLE in the config (null = \"always on\"), so narrow\n // to a branch NAME here; anything else means no branch scoping.\n const rawBranch = rule?.options['turnOffRuleWhileOnBranch'];\n const branch = typeof rawBranch === 'string' ? rawBranch : 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"]}
|
|
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 * A built-in floor (bare dir names + globs, e.g. **\\/*.d.ts and **\\/jest.config.ts) exempts files from\n * BOTH layers. The configured `excludePaths` — this rule's and the repo's top-level one — are the\n * framework-owned exclusions (#1253): the rule runs inside a RuleFileScope carrying them, so the changed-file\n * listing never contains a file they match. They ADD to the floor.\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\": [...], // globs, e.g. \"**\\/codegen.ts\", \"scripts/**\"\n * \"allowedRootFiles\": [...],\n * \"turnOffRuleUntilEpoch\": 0, // epoch SECONDS; skip the rule until it passes\n * \"turnOffRuleWhileOnBranch\": null // branch name; skip the rule while it is checked out\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 {\n loadAndValidate,\n isPathExcluded,\n shouldSkipRule,\n detectBase,\n getChangedFiles,\n RuleFileExclusions,\n RuleFileScope,\n} from '@webpieces/rules-config';\nimport * as path from 'path';\nimport * as fs from 'fs';\n\nexport type ValidateTsInSrcMode = 'OFF' | 'NEW_AND_MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n allowedRootFiles?: string[];\n turnOffRuleUntilEpoch?: number;\n turnOffRuleWhileOnBranch?: string | null;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst BUILT_IN_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 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 a glob for the containing directory (e.g. \"tools/**\") to validate-ts-in-src.excludePaths');\n console.error(' in the validate-ts-in-src entry of webpieces.config.json (or its owner rule file), or to');\n console.error(' the top-level excludePaths for every rule; 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 // git diff --name-only lists DELETED paths too (renames = delete+add);\n // a file that no longer exists cannot be an orphan - skip it\n const changedFiles = getChangedFiles(workspaceRoot, base, head)\n .filter((relPath: string) => fs.existsSync(path.join(workspaceRoot, relPath)));\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 loaded = loadAndValidate(context.root);\n const shared = loaded.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['turnOffRuleUntilEpoch'] as number | undefined;\n // turnOffRuleWhileOnBranch is required-but-NULLABLE in the config (null = \"always on\"), so narrow\n // to a branch NAME here; anything else means no branch scoping.\n const rawBranch = rule?.options['turnOffRuleWhileOnBranch'];\n const branch = typeof rawBranch === 'string' ? rawBranch : 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 // The framework-owned exclusions (#1253): the repo's top-level list plus this rule's own.\n const exclusions = new RuleFileExclusions(\n 'validate-ts-in-src',\n loaded.excludePaths.paths,\n (rule?.options['excludePaths'] as string[] | undefined) ?? [],\n );\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 new RuleFileScope().runWithin(exclusions, () =>\n runModifiedFilesMode(workspaceRoot, BUILT_IN_EXCLUDE_PATHS, allowedRootFiles),\n );\n}\n"]}
|
|
@@ -10,11 +10,6 @@
|
|
|
10
10
|
"default": "NEW_AND_MODIFIED_FILES",
|
|
11
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
|
-
"excludePaths": {
|
|
14
|
-
"type": "array",
|
|
15
|
-
"items": { "type": "string" },
|
|
16
|
-
"description": "Holistic exclusion list applied to BOTH layers. Each entry is either a bare directory/segment name matched at any depth (e.g. node_modules, dist, scripts) or a glob matched against the workspace-relative path (e.g. '**/*.d.ts', '**/codegen.ts'). Defaults: node_modules, dist, .nx, .git, architecture, tmp, scripts, **/*.d.ts, **/jest.config.ts. node_modules_* backup directories are always skipped. Override replaces the defaults — re-list any defaults you want to keep."
|
|
17
|
-
},
|
|
18
13
|
"allowedRootFiles": {
|
|
19
14
|
"type": "array",
|
|
20
15
|
"items": { "type": "string" },
|
|
@@ -48,8 +48,9 @@ class WireClosureRule {
|
|
|
48
48
|
if (new rule_gate_1.RuleGate().skipReason(workspaceRoot, 'required-type-suffix', true) !== null) {
|
|
49
49
|
return WireClosureRule.withoutSuffixes();
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const loaded = (0, rules_config_2.loadAndValidate)(workspaceRoot);
|
|
52
|
+
const rule = loaded.resolved.rules.get('required-type-suffix');
|
|
53
|
+
return new WireClosureRule(rule.options['entries'], new wire_suffix_scope_1.WireSuffixScope(workspaceRoot, rule.options['mode'], new rules_config_3.RuleFileExclusions('required-type-suffix', loaded.excludePaths.paths, rule.options['excludePaths'] ?? []), new rules_config_3.DiffScope()));
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
exports.WireClosureRule = WireClosureRule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire-closure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/wire-closure.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;;AAEH,mDAA6B;AAE7B,0DAA6E;AAC7E,0DAA0D;AAG1D,oDAA+C;AAC/C,4CAAwC;AACxC,0DAAoD;AAEpD,2DAAsD;AAEtD,MAAM,YAAY,GAAG,SAAS,CAAC;AAE/B,iGAAiG;AACjG,MAAa,eAAe;IAEJ;IACA;IAFpB,YACoB,aAAyC,EACzC,KAAkC;QADlC,kBAAa,GAAb,aAAa,CAA4B;QACzC,UAAK,GAAL,KAAK,CAA6B;IACnD,CAAC;IAEJ,6FAA6F;IAC7F,8EAA8E;IAC9E,MAAM,CAAC,eAAe;QAClB,OAAO,IAAI,eAAe,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED,mGAAmG;IACnG,8EAA8E;IAC9E,MAAM,CAAC,UAAU,CAAC,aAAqB;QACnC,IAAI,IAAI,oBAAQ,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,sBAAsB,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAClF,OAAO,eAAe,CAAC,eAAe,EAAE,CAAC;QAC7C,CAAC;QACD,MAAM,IAAI,GAAG,IAAA,8BAAe,EAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAE,CAAC;QACxF,OAAO,IAAI,eAAe,CACtB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAsB,EAC5C,IAAI,mCAAe,CACf,aAAa,EACb,IAAI,CAAC,OAAO,CAAC,MAAM,CAAqB,EACvC,IAAI,CAAC,OAAO,CAAC,cAAc,CAA0B,IAAI,EAAE,EAC5D,IAAI,wBAAS,EAAE,CAClB,CACJ,CAAC;IACN,CAAC;CACJ;AA7BD,0CA6BC;AAED,mFAAmF;AACnF,MAAa,kBAAkB;IAEP;IACA;IAFpB,YACoB,IAAY,EACZ,IAAY;QADZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;IAC7B,CAAC;CACP;AALD,gDAKC;AAED,+EAA+E;AAC/E,MAAM,SAAS;IAES;IACA;IAFpB,YACoB,IAAiB,EACjB,GAAW;QADX,SAAI,GAAJ,IAAI,CAAa;QACjB,QAAG,GAAH,GAAG,CAAQ;IAC5B,CAAC;CACP;AAED,yFAAyF;AACzF,MAAa,WAAW;IAKC;IAEA;IANJ,KAAK,CAAc;IACnB,MAAM,GAAG,IAAI,6BAAc,EAAE,CAAC;IAE/C,YACqB,aAAqB,EACtC,YAAsC,EACrB,IAAqB;QAFrB,kBAAa,GAAb,aAAa,CAAQ;QAErB,SAAI,GAAJ,IAAI,CAAiB;QAEtC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;aAClC,MAAM,CAAC,CAAC,IAAiB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC;aACpE,GAAG,CAAC,CAAC,IAAiB,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACxF,+DAA+D;aAC9D,IAAI,CAAC,CAAC,CAAY,EAAE,CAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,IAAoB,EAAE,OAAe;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACtD,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,IAAA,2BAAW,EAAC,KAAK,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC;QAC/C,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,YAAY,cAAc,KAAK,CAAC,IAAI,WAAW,IAAI,GAAG,CAAC;QAC/F,MAAM,QAAQ,GAAyB,EAAE,CAAC;QAC1C,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CACT,IAAI,kBAAkB,CAClB,IAAI,IAAI,CAAC,IAAI,2CAA2C,QAAQ,gCAAgC;gBAC5F,4EAA4E,EAChF,SAAS,IAAI,CAAC,IAAI,uEAAuE;gBACrF,oCAAoC,KAAK,CAAC,IAAI,0CAA0C;gBACxF,gGAAgG,CACvG,CACJ,CAAC;QACN,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAChC,OAAO,EACP,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CACvB,CAAC,KAAsB,EAAE,EAAE,CAAC,IAAI,iCAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CACzE,CACJ,CAAC;QACF,IACI,SAAS,KAAK,SAAS;YACvB,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM;YACvB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CACrB,IAAI,CAAC,IAAI,EACT,OAAO,EACP,KAAK,CAAC,IAAI,EACV,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAe,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CACtD,KAAK,IAAI;YACV,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,EACzD,CAAC;YACC,QAAQ,CAAC,IAAI,CACT,IAAI,kBAAkB,CAClB,IAAI,IAAI,CAAC,IAAI,6BAA6B,OAAO,8BAA8B;gBAC3E,IAAI,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,0CAA0C;gBACjF,IAAI,SAAS,CAAC,IAAI,iBAAiB,EACvC,WAAW,IAAI,CAAC,IAAI,sBAAsB,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB;gBACjG,qFAAqF,CAC5F,CACJ,CAAC;QACN,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;IAEO,YAAY,CAAC,IAAY,EAAE,QAA2B;QAC1D,OAAO,QAAQ,CAAC,IAAI,CAChB,CAAC,MAAc,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC3E,CAAC;IACN,CAAC;IAEO,OAAO,CAAC,GAAW;QACvB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC;QAClF,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAhFD,kCAgFC","sourcesContent":["/**\n * The WIRE CLOSURE of a contract (#1064, D4): every named type an `@ApiPath` contract reaches must be\n * DECLARED in a `role:api-lib` project, and must end in a suffix that project's `required-type-suffix`\n * entry demands.\n *\n * Why it lives here and not in the dependency rule: `api-lib-dependencies` only sees project EDGES. A\n * relative-path import into another library, or a DTO library that itself reaches outside, puts a type\n * on the wire without any edge the graph rule could refuse. The extractor already follows every type a\n * contract reaches through the compiler — the same walk `api-rules-for-openapi` / `api-rules-for-mcp`\n * run, with no generate tag needed — so this is one more question asked of each type that walk found.\n *\n * Every api-lib rule before this one (`required-type-suffix`, `one-enum-spelling-in-api-lib`,\n * `no-utility-types-in-api-lib`, `no-inline-import-in-api-lib`) reads only source files INSIDE the api\n * library. A type declared in another package was, by construction, outside every one of them — which is\n * how `AiProvider` and the `BrowserTelemetry` trio reached the wire from a general library unchecked.\n *\n * A type declared OUTSIDE the workspace (a published package under node_modules) is not judged here:\n * its own package owns its schema, and the generator's fail-closed components check covers it.\n */\n\nimport * as path from 'path';\nimport { DocumentedType } from '@webpieces/api-doc-model';\nimport { FirstPathMatch, PathMatchCandidate } from '@webpieces/rules-config';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport { WireSuffixEntry } from './wire-suffix-entry';\nimport { ProjectInfo } from '../project-info';\nimport { resolveRole } from '../role-resolver';\nimport { RuleGate } from '../rule-gate';\nimport { DiffScope } from '@webpieces/rules-config';\nimport { ModifiedCodeMode } from '@webpieces/rules-sdk';\nimport { WireSuffixScope } from './wire-suffix-scope';\n\nconst API_LIB_ROLE = 'api-lib';\n\n/** The `required-type-suffix` entries the wire closure enforces; empty when that rule is OFF. */\nexport class WireClosureRule {\n constructor(\n public readonly suffixEntries: readonly WireSuffixEntry[],\n public readonly scope: WireSuffixScope | undefined,\n ) {}\n\n /** Only the role check — what a unit test or a repo with `required-type-suffix` OFF gets. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static withoutSuffixes(): WireClosureRule {\n return new WireClosureRule([], undefined);\n }\n\n /** `required-type-suffix`'s entries, unless that rule is OFF or time-boxed / branch-scoped off. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromConfig(workspaceRoot: string): WireClosureRule {\n if (new RuleGate().skipReason(workspaceRoot, 'required-type-suffix', true) !== null) {\n return WireClosureRule.withoutSuffixes();\n }\n const rule = loadAndValidate(workspaceRoot).resolved.rules.get('required-type-suffix')!;\n return new WireClosureRule(\n rule.options['entries'] as WireSuffixEntry[],\n new WireSuffixScope(\n workspaceRoot,\n rule.options['mode'] as ModifiedCodeMode,\n (rule.options['allowedPaths'] as string[] | undefined) ?? [],\n new DiffScope(),\n ),\n );\n }\n}\n\n/** ONE wire-closure finding: what is wrong with a reached type, and what to do. */\nexport class WireClosureVerdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n/** A workspace project root, absolute, for the longest-prefix owner lookup. */\nclass OwnedRoot {\n constructor(\n public readonly info: ProjectInfo,\n public readonly abs: string,\n ) {}\n}\n\n/** Judges every type a contract reaches against the api-lib role and the suffix rule. */\nexport class WireClosure {\n private readonly roots: OwnedRoot[];\n private readonly picker = new FirstPathMatch();\n\n constructor(\n private readonly workspaceRoot: string,\n projectInfos: Map<string, ProjectInfo>,\n private readonly rule: WireClosureRule,\n ) {\n this.roots = [...projectInfos.values()]\n .filter((info: ProjectInfo) => info.root !== '' && info.root !== '.')\n .map((info: ProjectInfo) => new OwnedRoot(info, path.resolve(workspaceRoot, info.root)))\n // Longest root first so a nested project wins over its parent.\n .sort((a: OwnedRoot, b: OwnedRoot) => b.abs.length - a.abs.length);\n }\n\n /** Every verdict for ONE reached type, declared in the file `absFile`. */\n judge(type: DocumentedType, absFile: string): WireClosureVerdict[] {\n const abs = path.resolve(this.workspaceRoot, absFile);\n if (abs.split(path.sep).includes('node_modules')) return [];\n const owner = this.ownerOf(abs);\n if (owner === null) return [];\n const relFile = path.relative(this.workspaceRoot, abs).split(path.sep).join('/');\n const role = resolveRole(owner).role ?? 'none';\n const declared = `${type.packageName ?? 'no package'} (project '${owner.name}', role:${role})`;\n const verdicts: WireClosureVerdict[] = [];\n if (role !== API_LIB_ROLE) {\n verdicts.push(\n new WireClosureVerdict(\n `'${type.name}' goes over the wire but is declared in ${declared}, which is not a role:api-lib ` +\n 'project — every type a contract reaches must be declared in an api library',\n `Move '${type.name}' into a role:api-lib project — as a '…Dto' string enum when it is a ` +\n `string-literal union. Retagging '${owner.name}' is not a way out unless it holds only ` +\n 'contracts and wire types (validate-api-lib-tag refuses a role:api-lib with an implementation).',\n ),\n );\n }\n const governing = this.picker.winner(\n relFile,\n this.rule.suffixEntries.map(\n (entry: WireSuffixEntry) => new PathMatchCandidate(entry.paths, entry),\n ),\n );\n if (\n governing !== undefined &&\n !governing.entry.exempt &&\n this.rule.scope?.includes(\n type.name,\n relFile,\n owner.root,\n type.location,\n this.roots.map((root: OwnedRoot) => root.info.root),\n ) === true &&\n !this.endsInSuffix(type.name, governing.entry.suffixes)\n ) {\n verdicts.push(\n new WireClosureVerdict(\n `'${type.name}' goes over the wire from ${relFile} but does not end in one of ` +\n `[${governing.entry.suffixes.join(', ')}], which the required-type-suffix entry ` +\n `'${governing.glob}' demands of it`,\n `Rename '${type.name}' to end in one of ${governing.entry.suffixes.join(', ')} — the suffix tells a ` +\n 'reader which layer a type belongs to, and a wire type is the layer a partner reads.',\n ),\n );\n }\n return verdicts;\n }\n\n private endsInSuffix(name: string, suffixes: readonly string[]): boolean {\n return suffixes.some(\n (suffix: string) => name.length > suffix.length && name.endsWith(suffix),\n );\n }\n\n private ownerOf(abs: string): ProjectInfo | null {\n for (const root of this.roots) {\n if (abs === root.abs || abs.startsWith(root.abs + path.sep)) return root.info;\n }\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"wire-closure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/wire-closure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The WIRE CLOSURE of a contract (#1064, D4): every named type an `@ApiPath` contract reaches must be\n * DECLARED in a `role:api-lib` project, and must end in a suffix that project's `required-type-suffix`\n * entry demands.\n *\n * Why it lives here and not in the dependency rule: `api-lib-dependencies` only sees project EDGES. A\n * relative-path import into another library, or a DTO library that itself reaches outside, puts a type\n * on the wire without any edge the graph rule could refuse. The extractor already follows every type a\n * contract reaches through the compiler — the same walk `api-rules-for-openapi` / `api-rules-for-mcp`\n * run, with no generate tag needed — so this is one more question asked of each type that walk found.\n *\n * Every api-lib rule before this one (`required-type-suffix`, `one-enum-spelling-in-api-lib`,\n * `no-utility-types-in-api-lib`, `no-inline-import-in-api-lib`) reads only source files INSIDE the api\n * library. A type declared in another package was, by construction, outside every one of them — which is\n * how `AiProvider` and the `BrowserTelemetry` trio reached the wire from a general library unchecked.\n *\n * A type declared OUTSIDE the workspace (a published package under node_modules) is not judged here:\n * its own package owns its schema, and the generator's fail-closed components check covers it.\n */\n\nimport * as path from 'path';\nimport { DocumentedType } from '@webpieces/api-doc-model';\nimport { FirstPathMatch, PathMatchCandidate } from '@webpieces/rules-config';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport { WireSuffixEntry } from './wire-suffix-entry';\nimport { ProjectInfo } from '../project-info';\nimport { resolveRole } from '../role-resolver';\nimport { RuleGate } from '../rule-gate';\nimport { DiffScope, RuleFileExclusions } from '@webpieces/rules-config';\nimport { ModifiedCodeMode } from '@webpieces/rules-sdk';\nimport { WireSuffixScope } from './wire-suffix-scope';\n\nconst API_LIB_ROLE = 'api-lib';\n\n/** The `required-type-suffix` entries the wire closure enforces; empty when that rule is OFF. */\nexport class WireClosureRule {\n constructor(\n public readonly suffixEntries: readonly WireSuffixEntry[],\n public readonly scope: WireSuffixScope | undefined,\n ) {}\n\n /** Only the role check — what a unit test or a repo with `required-type-suffix` OFF gets. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static withoutSuffixes(): WireClosureRule {\n return new WireClosureRule([], undefined);\n }\n\n /** `required-type-suffix`'s entries, unless that rule is OFF or time-boxed / branch-scoped off. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromConfig(workspaceRoot: string): WireClosureRule {\n if (new RuleGate().skipReason(workspaceRoot, 'required-type-suffix', true) !== null) {\n return WireClosureRule.withoutSuffixes();\n }\n const loaded = loadAndValidate(workspaceRoot);\n const rule = loaded.resolved.rules.get('required-type-suffix')!;\n return new WireClosureRule(\n rule.options['entries'] as WireSuffixEntry[],\n new WireSuffixScope(\n workspaceRoot,\n rule.options['mode'] as ModifiedCodeMode,\n new RuleFileExclusions(\n 'required-type-suffix',\n loaded.excludePaths.paths,\n (rule.options['excludePaths'] as string[] | undefined) ?? [],\n ),\n new DiffScope(),\n ),\n );\n }\n}\n\n/** ONE wire-closure finding: what is wrong with a reached type, and what to do. */\nexport class WireClosureVerdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n/** A workspace project root, absolute, for the longest-prefix owner lookup. */\nclass OwnedRoot {\n constructor(\n public readonly info: ProjectInfo,\n public readonly abs: string,\n ) {}\n}\n\n/** Judges every type a contract reaches against the api-lib role and the suffix rule. */\nexport class WireClosure {\n private readonly roots: OwnedRoot[];\n private readonly picker = new FirstPathMatch();\n\n constructor(\n private readonly workspaceRoot: string,\n projectInfos: Map<string, ProjectInfo>,\n private readonly rule: WireClosureRule,\n ) {\n this.roots = [...projectInfos.values()]\n .filter((info: ProjectInfo) => info.root !== '' && info.root !== '.')\n .map((info: ProjectInfo) => new OwnedRoot(info, path.resolve(workspaceRoot, info.root)))\n // Longest root first so a nested project wins over its parent.\n .sort((a: OwnedRoot, b: OwnedRoot) => b.abs.length - a.abs.length);\n }\n\n /** Every verdict for ONE reached type, declared in the file `absFile`. */\n judge(type: DocumentedType, absFile: string): WireClosureVerdict[] {\n const abs = path.resolve(this.workspaceRoot, absFile);\n if (abs.split(path.sep).includes('node_modules')) return [];\n const owner = this.ownerOf(abs);\n if (owner === null) return [];\n const relFile = path.relative(this.workspaceRoot, abs).split(path.sep).join('/');\n const role = resolveRole(owner).role ?? 'none';\n const declared = `${type.packageName ?? 'no package'} (project '${owner.name}', role:${role})`;\n const verdicts: WireClosureVerdict[] = [];\n if (role !== API_LIB_ROLE) {\n verdicts.push(\n new WireClosureVerdict(\n `'${type.name}' goes over the wire but is declared in ${declared}, which is not a role:api-lib ` +\n 'project — every type a contract reaches must be declared in an api library',\n `Move '${type.name}' into a role:api-lib project — as a '…Dto' string enum when it is a ` +\n `string-literal union. Retagging '${owner.name}' is not a way out unless it holds only ` +\n 'contracts and wire types (validate-api-lib-tag refuses a role:api-lib with an implementation).',\n ),\n );\n }\n const governing = this.picker.winner(\n relFile,\n this.rule.suffixEntries.map(\n (entry: WireSuffixEntry) => new PathMatchCandidate(entry.paths, entry),\n ),\n );\n if (\n governing !== undefined &&\n !governing.entry.exempt &&\n this.rule.scope?.includes(\n type.name,\n relFile,\n owner.root,\n type.location,\n this.roots.map((root: OwnedRoot) => root.info.root),\n ) === true &&\n !this.endsInSuffix(type.name, governing.entry.suffixes)\n ) {\n verdicts.push(\n new WireClosureVerdict(\n `'${type.name}' goes over the wire from ${relFile} but does not end in one of ` +\n `[${governing.entry.suffixes.join(', ')}], which the required-type-suffix entry ` +\n `'${governing.glob}' demands of it`,\n `Rename '${type.name}' to end in one of ${governing.entry.suffixes.join(', ')} — the suffix tells a ` +\n 'reader which layer a type belongs to, and a wire type is the layer a partner reads.',\n ),\n );\n }\n return verdicts;\n }\n\n private endsInSuffix(name: string, suffixes: readonly string[]): boolean {\n return suffixes.some(\n (suffix: string) => name.length > suffix.length && name.endsWith(suffix),\n );\n }\n\n private ownerOf(abs: string): ProjectInfo | null {\n for (const root of this.roots) {\n if (abs === root.abs || abs.startsWith(root.abs + path.sep)) return root.info;\n }\n return null;\n }\n}\n"]}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { DiffScope } from '@webpieces/rules-config';
|
|
1
|
+
import { DiffScope, RuleFileExclusions } from '@webpieces/rules-config';
|
|
2
2
|
import { ModifiedCodeMode } from '@webpieces/rules-sdk';
|
|
3
3
|
/** Suffix enforcement keeps its own rollout scope, independently of the enclosing doc rule. */
|
|
4
4
|
export declare class WireSuffixScope {
|
|
5
5
|
private readonly root;
|
|
6
6
|
private readonly mode;
|
|
7
|
-
private readonly
|
|
7
|
+
private readonly exclusions;
|
|
8
8
|
private readonly diff;
|
|
9
9
|
private readonly changed;
|
|
10
10
|
private readonly lines;
|
|
11
11
|
private readonly base;
|
|
12
12
|
private readonly head;
|
|
13
|
-
constructor(root: string, mode: ModifiedCodeMode,
|
|
13
|
+
constructor(root: string, mode: ModifiedCodeMode, exclusions: RuleFileExclusions, diff: DiffScope);
|
|
14
14
|
includes(name: string, relFile: string, projectRoot: string, location: string, projectRoots: readonly string[]): boolean;
|
|
15
15
|
private declarationAt;
|
|
16
16
|
private fileInScope;
|
|
@@ -10,16 +10,19 @@ const rules_config_1 = require("@webpieces/rules-config");
|
|
|
10
10
|
class WireSuffixScope {
|
|
11
11
|
root;
|
|
12
12
|
mode;
|
|
13
|
-
|
|
13
|
+
exclusions;
|
|
14
14
|
diff;
|
|
15
15
|
changed;
|
|
16
16
|
lines = new Map();
|
|
17
17
|
base;
|
|
18
18
|
head;
|
|
19
|
-
constructor(root, mode,
|
|
19
|
+
constructor(root, mode,
|
|
20
|
+
// required-type-suffix's framework-owned path exclusions (#1253): the repo's top-level
|
|
21
|
+
// excludePaths plus the rule's own — the same RuleFileExclusions the code-rules runtime applies.
|
|
22
|
+
exclusions, diff) {
|
|
20
23
|
this.root = root;
|
|
21
24
|
this.mode = mode;
|
|
22
|
-
this.
|
|
25
|
+
this.exclusions = exclusions;
|
|
23
26
|
this.diff = diff;
|
|
24
27
|
const range = mode === 'RUN_EVERY_TIME' ? undefined : diff.resolveBase(root);
|
|
25
28
|
this.base = range?.base;
|
|
@@ -32,7 +35,7 @@ class WireSuffixScope {
|
|
|
32
35
|
: diff.getChangedFiles(root, this.base, this.head, { tsOnly: false }));
|
|
33
36
|
}
|
|
34
37
|
includes(name, relFile, projectRoot, location, projectRoots) {
|
|
35
|
-
if (this.mode === 'OFF' ||
|
|
38
|
+
if (this.mode === 'OFF' || this.exclusions.excludes(relFile))
|
|
36
39
|
return false;
|
|
37
40
|
if (relFile.endsWith('.d.ts') ||
|
|
38
41
|
/\.(spec|test)\.ts$/.test(relFile) ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire-suffix-scope.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/wire-suffix-scope.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,uDAAiC;AACjC,
|
|
1
|
+
{"version":3,"file":"wire-suffix-scope.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/wire-suffix-scope.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport { DiffScope, hasDisable, RuleFileExclusions } from '@webpieces/rules-config';\nimport { ModifiedCodeMode } from '@webpieces/rules-sdk';\n\n/** Suffix enforcement keeps its own rollout scope, independently of the enclosing doc rule. */\nexport class WireSuffixScope {\n private readonly changed: Set<string> | null;\n private readonly lines = new Map<string, Set<number>>();\n private readonly base: string | undefined;\n private readonly head: string | undefined;\n\n constructor(\n private readonly root: string,\n private readonly mode: ModifiedCodeMode,\n // required-type-suffix's framework-owned path exclusions (#1253): the repo's top-level\n // excludePaths plus the rule's own — the same RuleFileExclusions the code-rules runtime applies.\n private readonly exclusions: RuleFileExclusions,\n private readonly diff: DiffScope,\n ) {\n const range = mode === 'RUN_EVERY_TIME' ? undefined : diff.resolveBase(root);\n this.base = range?.base;\n this.head = range?.head;\n this.changed =\n mode === 'RUN_EVERY_TIME'\n ? null\n : new Set(\n this.base === undefined\n ? []\n : diff.getChangedFiles(root, this.base, this.head, { tsOnly: false }),\n );\n }\n\n includes(\n name: string,\n relFile: string,\n projectRoot: string,\n location: string,\n projectRoots: readonly string[],\n ): boolean {\n if (this.mode === 'OFF' || this.exclusions.excludes(relFile)) return false;\n if (\n relFile.endsWith('.d.ts') ||\n /\\.(spec|test)\\.ts$/.test(relFile) ||\n relFile.includes('__tests__/')\n )\n return false;\n if (!this.fileInScope(relFile, projectRoot, projectRoots)) return false;\n const file = path.resolve(this.root, relFile);\n const text = fs.readFileSync(file, 'utf8');\n const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);\n const declaredLine = Number(/:(\\d+):\\d+$/.exec(location)?.[1]);\n const declaration = this.declarationAt(source, name, declaredLine);\n if (declaration === undefined) return false;\n const named = declaration as\n | ts.ClassDeclaration\n | ts.InterfaceDeclaration\n | ts.EnumDeclaration\n | ts.TypeAliasDeclaration;\n const line = source.getLineAndCharacterOfPosition(named.name!.getStart(source)).line + 1;\n const textLines = text.split('\\n');\n if (\n hasDisable(textLines[line - 1] ?? '', 'required-type-suffix') ||\n hasDisable(textLines[line - 2] ?? '', 'required-type-suffix')\n )\n return false;\n return this.mode !== 'NEW_AND_MODIFIED_CODE' || this.changedLines(relFile).has(line);\n }\n\n private declarationAt(\n source: ts.SourceFile,\n name: string,\n line: number,\n ): ts.DeclarationStatement | undefined {\n let found: ts.DeclarationStatement | undefined;\n const visit = (node: ts.Node): void => {\n if (\n (ts.isClassDeclaration(node) ||\n ts.isInterfaceDeclaration(node) ||\n ts.isEnumDeclaration(node) ||\n ts.isTypeAliasDeclaration(node)) &&\n node.name?.text === name &&\n source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1 === line\n )\n found = node;\n if (found === undefined) ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n }\n\n private fileInScope(\n relFile: string,\n projectRoot: string,\n projectRoots: readonly string[],\n ): boolean {\n if (this.changed === null) return true;\n if (this.mode !== 'MODIFIED_PROJECTS') return this.changed.has(relFile);\n return [...this.changed].some((file: string) => {\n const owner = projectRoots\n .filter((root: string) => file === root || file.startsWith(`${root}/`))\n .sort((a: string, b: string) => b.length - a.length)[0];\n return owner === projectRoot;\n });\n }\n\n private changedLines(relFile: string): ReadonlySet<number> {\n let lines = this.lines.get(relFile);\n if (lines === undefined) {\n lines =\n this.base === undefined\n ? new Set<number>()\n : this.diff.getChangedLineNumbers(\n this.diff.getFileDiff(this.root, relFile, this.base, this.head),\n );\n this.lines.set(relFile, lines);\n }\n return lines;\n }\n}\n"]}
|
package/src/pack-build-runner.js
CHANGED
|
@@ -16,7 +16,7 @@ class PackBuildRunner {
|
|
|
16
16
|
.groups(loaded.ruleRegistry, 'build')
|
|
17
17
|
.filter((group) => group.providerPack !== rule_pack_1.rulePackManifest.packageName);
|
|
18
18
|
for (const group of groups) {
|
|
19
|
-
const request = modules.request(root, loaded.rulesConfig, group);
|
|
19
|
+
const request = modules.request(root, loaded.rulesConfig, group, loaded.excludePaths.paths);
|
|
20
20
|
const exports = modules.load(group, loaded.ruleRegistry.manifestModules, loader);
|
|
21
21
|
const checks = await modules.createBuildPolicies(exports, request);
|
|
22
22
|
for (const check of checks) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack-build-runner.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/pack-build-runner.ts"],"names":[],"mappings":";;;AAAA,oDAAuF;AACvF,0DAQiC;AACjC,uCAAoC;AACpC,2CAA+C;AAE/C,MAAM,eAAgB,SAAQ,2BAA4B;CAAG;AAE7D,kGAAkG;AAClG,MAAa,eAAe;IACxB,KAAK,CAAC,GAAG,CAAC,IAAY,EAAE,MAAoB;QACxC,MAAM,OAAO,GAAG,IAAI,iCAAkB,EAAE,EACpC,MAAM,GAAG,IAAI,0CAA2B,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,MAAM,MAAM,GAAG,OAAO;aACjB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC;aACpC,MAAM,CACH,CAAC,KAA+B,EAAE,EAAE,CAChC,KAAK,CAAC,YAAY,KAAK,4BAAgB,CAAC,WAAW,CAC1D,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAC3B,IAAI,EACJ,MAAM,CAAC,WAA2C,EAClD,KAAK,
|
|
1
|
+
{"version":3,"file":"pack-build-runner.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/pack-build-runner.ts"],"names":[],"mappings":";;;AAAA,oDAAuF;AACvF,0DAQiC;AACjC,uCAAoC;AACpC,2CAA+C;AAE/C,MAAM,eAAgB,SAAQ,2BAA4B;CAAG;AAE7D,kGAAkG;AAClG,MAAa,eAAe;IACxB,KAAK,CAAC,GAAG,CAAC,IAAY,EAAE,MAAoB;QACxC,MAAM,OAAO,GAAG,IAAI,iCAAkB,EAAE,EACpC,MAAM,GAAG,IAAI,0CAA2B,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,MAAM,MAAM,GAAG,OAAO;aACjB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC;aACpC,MAAM,CACH,CAAC,KAA+B,EAAE,EAAE,CAChC,KAAK,CAAC,YAAY,KAAK,4BAAgB,CAAC,WAAW,CAC1D,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAC3B,IAAI,EACJ,MAAM,CAAC,WAA2C,EAClD,KAAK,EACL,MAAM,CAAC,YAAY,CAAC,KAAK,CAC5B,CAAC;YACF,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;YACjF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,UAAU,GAAG,IAAI,eAAe,CAClC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAmB,EAClD,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,SAAS,CAClB,CAAC;gBACF,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;oBAAE,SAAS;gBAC5D,0HAA0H;gBAC1H,IAAI,CAAC;oBACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACrC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;wBAAE,OAAO,GAAG,KAAK,CAAC;oBAC5D,6GAA6G;gBAC7G,CAAC;gBAAC,OAAO,GAAY,EAAE,CAAC;oBACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;oBAC3B,OAAO,CAAC,KAAK,CACT,KAAK,YAAY,4BAAa;wBAC1B,CAAC,CAAC,IAAA,qCAAsB,EAAC,KAAK,CAAC;wBAC/B,CAAC,CAAC,gBAAgB,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,OAAO,EAAE,CAC9D,CAAC;oBACF,OAAO,GAAG,KAAK,CAAC;gBACpB,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,IAAI,6BAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;CACJ;AA7CD,0CA6CC","sourcesContent":["import { BaseRuleConfig, ConfigObject, BuildPolicyResult } from '@webpieces/rules-sdk';\nimport {\n AbstractRule,\n LoadedConfig,\n NodeRuleRuntimeModuleLoader,\n RuleRuntimeModules,\n RuntimeContributionGroup,\n renderRuleFailForHuman,\n RuleFailError,\n} from '@webpieces/rules-config';\nimport { toError } from './toError';\nimport { rulePackManifest } from './rule-pack';\n\nclass BuildActivation extends AbstractRule<BaseRuleConfig> {}\n\n/** Native checks retain their targets; one generic target runs other declared build providers. */\nexport class PackBuildRunner {\n async run(root: string, loaded: LoadedConfig): Promise<BuildPolicyResult> {\n const modules = new RuleRuntimeModules(),\n loader = new NodeRuleRuntimeModuleLoader(root);\n let success = true;\n const groups = modules\n .groups(loaded.ruleRegistry, 'build')\n .filter(\n (group: RuntimeContributionGroup) =>\n group.providerPack !== rulePackManifest.packageName,\n );\n for (const group of groups) {\n const request = modules.request(\n root,\n loaded.rulesConfig as Record<string, ConfigObject>,\n group,\n loaded.excludePaths.paths,\n );\n const exports = modules.load(group, loaded.ruleRegistry.manifestModules, loader);\n const checks = await modules.createBuildPolicies(exports, request);\n for (const check of checks) {\n const activation = new BuildActivation(\n request.configs[check.configKey] as BaseRuleConfig,\n check.name,\n check.configKey,\n );\n if (!activation.shouldRun() || !check.shouldRun()) continue;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- isolate checks and preserve human-facing policy failures\n try {\n const result = await check.run(root);\n if (!result || result.success !== true) success = false;\n // webpieces-disable no-any-unknown -- external JSON or runtime exports are validated before policy execution\n } catch (err: unknown) {\n const error = toError(err);\n console.error(\n error instanceof RuleFailError\n ? renderRuleFailForHuman(error)\n : `Build policy ${check.name} failed: ${error.message}`,\n );\n success = false;\n }\n }\n }\n return new BuildPolicyResult(success);\n }\n}\n"]}
|