@webpieces/nx-webpieces-rules 0.4.831 → 0.4.833
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 +8 -8
- package/src/executors/generate/executor.js +2 -0
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/openapi-components-generate/executor.d.ts +2 -1
- package/src/executors/openapi-components-generate/executor.js +5 -2
- package/src/executors/openapi-components-generate/executor.js.map +1 -1
- package/src/executors/openapi-generate/executor.d.ts +15 -1
- package/src/executors/openapi-generate/executor.js +30 -2
- package/src/executors/openapi-generate/executor.js.map +1 -1
- package/src/executors/validate-api-lib-tag/executor.d.ts +5 -3
- package/src/executors/validate-api-lib-tag/executor.js +7 -5
- package/src/executors/validate-api-lib-tag/executor.js.map +1 -1
- package/src/executors/validate-architecture-unchanged/executor.js +2 -0
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/src/lib/api-docs/components-wiring.d.ts +6 -0
- package/src/lib/api-docs/components-wiring.js +18 -1
- package/src/lib/api-docs/components-wiring.js.map +1 -1
- package/src/lib/api-usage/api-doc-rules-scan.d.ts +17 -1
- package/src/lib/api-usage/api-doc-rules-scan.js +28 -3
- package/src/lib/api-usage/api-doc-rules-scan.js.map +1 -1
- package/src/lib/api-usage/api-lib-tag-validator.d.ts +50 -14
- package/src/lib/api-usage/api-lib-tag-validator.js +189 -22
- package/src/lib/api-usage/api-lib-tag-validator.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +3 -1
- package/src/lib/api-usage/api-scanner.js +6 -3
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/api-usage/wire-closure.d.ts +49 -0
- package/src/lib/api-usage/wire-closure.js +126 -0
- package/src/lib/api-usage/wire-closure.js.map +1 -0
- package/src/lib/di-graph/analyzer-strategy.d.ts +1 -0
- package/src/lib/di-graph/analyzer-strategy.js +3 -0
- package/src/lib/di-graph/analyzer-strategy.js.map +1 -1
- package/src/lib/framework-resolver.d.ts +1 -1
- package/src/lib/framework-resolver.js +5 -2
- package/src/lib/framework-resolver.js.map +1 -1
- package/src/lib/graph-metadata.d.ts +9 -2
- package/src/lib/graph-metadata.js +12 -3
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-visualizer.js +6 -0
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/role-resolver.d.ts +9 -3
- package/src/lib/role-resolver.js +12 -4
- package/src/lib/role-resolver.js.map +1 -1
- package/src/lib/tag-truth.d.ts +106 -0
- package/src/lib/tag-truth.js +323 -0
- package/src/lib/tag-truth.js.map +1 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tag truth — the three GRAPH rules that make an nx `role:*` / `framework:*` tag true (#1064):
|
|
4
|
+
*
|
|
5
|
+
* api-lib-dependencies (D2) — a `role:api-lib` depends only on other `role:api-lib` projects plus a
|
|
6
|
+
* stated list of outside packages; a `role:api-client` may also import
|
|
7
|
+
* the packages its own config entry names (its SDK, inversify, …).
|
|
8
|
+
* api-lib-path (D10) — every api-lib / api-client lives under the configured api globs, and
|
|
9
|
+
* everything under them carries one of those two roles.
|
|
10
|
+
* framework-folder (D9) — the folder a library lives in and its framework set + role agree,
|
|
11
|
+
* both directions.
|
|
12
|
+
*
|
|
13
|
+
* They run beside `library-types-match-client` and `role-dependency` (graph-metadata.ts), in
|
|
14
|
+
* `architecture:generate` and `validate-architecture-unchanged`, AFTER enrichGraph has resolved every
|
|
15
|
+
* project's framework set and role — so a tag is read exactly one way. Every problem across every
|
|
16
|
+
* project is collected and thrown as one MetadataValidationError, before anything is written.
|
|
17
|
+
*
|
|
18
|
+
* There is NO default (`.claude/rules/no-rule-defaults.md`): each rule's fields come from
|
|
19
|
+
* webpieces.config.json, which must state them or fail to load. `TagTruthRules.none()` is what a unit
|
|
20
|
+
* test or a caller that configured nothing asked for — not a default.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.TagTruthCheck = exports.TagTruthValidator = exports.ProjectImports = exports.TagTruthRules = exports.FrameworkFolderRule = exports.FrameworkFolder = exports.ApiLibPathRule = exports.ApiLibDependenciesRule = exports.API_CLIENT_ROLE = exports.API_LIB_ROLE = void 0;
|
|
24
|
+
const tslib_1 = require("tslib");
|
|
25
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
26
|
+
const path = tslib_1.__importStar(require("path"));
|
|
27
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
28
|
+
const graph_metadata_1 = require("./graph-metadata");
|
|
29
|
+
const rule_gate_1 = require("./rule-gate");
|
|
30
|
+
const dep_usage_scanner_1 = require("./dep-usage-scanner");
|
|
31
|
+
const toError_1 = require("../toError");
|
|
32
|
+
exports.API_LIB_ROLE = 'api-lib';
|
|
33
|
+
exports.API_CLIENT_ROLE = 'api-client';
|
|
34
|
+
const API_ROLES = [exports.API_LIB_ROLE, exports.API_CLIENT_ROLE];
|
|
35
|
+
/** `api-lib-dependencies`, resolved: what an api library may import from outside the api-lib set. */
|
|
36
|
+
class ApiLibDependenciesRule {
|
|
37
|
+
apiLibPackages;
|
|
38
|
+
apiClientPackages;
|
|
39
|
+
constructor(
|
|
40
|
+
/** Package / project names (globs allowed) every api-lib and api-client may depend on. */
|
|
41
|
+
apiLibPackages,
|
|
42
|
+
/** `role:api-client` project → the extra packages it may depend on. */
|
|
43
|
+
apiClientPackages) {
|
|
44
|
+
this.apiLibPackages = apiLibPackages;
|
|
45
|
+
this.apiClientPackages = apiClientPackages;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.ApiLibDependenciesRule = ApiLibDependenciesRule;
|
|
49
|
+
/** `api-lib-path`, resolved: the globs every api project — and only api projects — lives under. */
|
|
50
|
+
class ApiLibPathRule {
|
|
51
|
+
paths;
|
|
52
|
+
constructor(paths) {
|
|
53
|
+
this.paths = paths;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ApiLibPathRule = ApiLibPathRule;
|
|
57
|
+
/** One `framework-folder` entry with its framework sets canonicalised (`a+b`, tokens sorted). */
|
|
58
|
+
class FrameworkFolder {
|
|
59
|
+
paths;
|
|
60
|
+
frameworkSets;
|
|
61
|
+
roles;
|
|
62
|
+
constructor(paths, frameworkSets, roles) {
|
|
63
|
+
this.paths = paths;
|
|
64
|
+
this.frameworkSets = frameworkSets;
|
|
65
|
+
this.roles = roles;
|
|
66
|
+
}
|
|
67
|
+
/** A framework set written in any order, canonicalised: `node+browser` → `browser+node`. */
|
|
68
|
+
// webpieces-disable no-function-outside-class -- static helper of this class
|
|
69
|
+
static canonical(frameworks) {
|
|
70
|
+
return [...new Set(frameworks.map((each) => each.trim()).filter((each) => each !== ''))]
|
|
71
|
+
.sort()
|
|
72
|
+
.join('+');
|
|
73
|
+
}
|
|
74
|
+
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
75
|
+
static fromEntry(entry) {
|
|
76
|
+
return new FrameworkFolder(entry.paths, entry.frameworkSets.map((set) => FrameworkFolder.canonical(set.split('+'))), entry.roles);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.FrameworkFolder = FrameworkFolder;
|
|
80
|
+
/** `framework-folder`, resolved. Entry ORDER matters: the first entry whose glob matches governs. */
|
|
81
|
+
class FrameworkFolderRule {
|
|
82
|
+
folders;
|
|
83
|
+
constructor(folders) {
|
|
84
|
+
this.folders = folders;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.FrameworkFolderRule = FrameworkFolderRule;
|
|
88
|
+
/**
|
|
89
|
+
* The three rules' switches, read from webpieces.config.json once. A rule that is OFF (or time-boxed /
|
|
90
|
+
* branch-scoped off) is `null`.
|
|
91
|
+
*/
|
|
92
|
+
class TagTruthRules {
|
|
93
|
+
apiLibDependencies;
|
|
94
|
+
apiLibPath;
|
|
95
|
+
frameworkFolder;
|
|
96
|
+
constructor(apiLibDependencies, apiLibPath, frameworkFolder) {
|
|
97
|
+
this.apiLibDependencies = apiLibDependencies;
|
|
98
|
+
this.apiLibPath = apiLibPath;
|
|
99
|
+
this.frameworkFolder = frameworkFolder;
|
|
100
|
+
}
|
|
101
|
+
/** Every rule off — what a caller that configured nothing asked for. */
|
|
102
|
+
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
103
|
+
static none() {
|
|
104
|
+
return new TagTruthRules(null, null, null);
|
|
105
|
+
}
|
|
106
|
+
anyEnabled() {
|
|
107
|
+
return this.apiLibDependencies !== null || this.apiLibPath !== null || this.frameworkFolder !== null;
|
|
108
|
+
}
|
|
109
|
+
/** `mode: OFF` and both escape hatches come from RuleGate, so there is ONE reading of them. */
|
|
110
|
+
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
111
|
+
static fromConfig(workspaceRoot) {
|
|
112
|
+
const gate = new rule_gate_1.RuleGate();
|
|
113
|
+
const rules = (0, rules_config_1.loadAndValidate)(workspaceRoot).resolved.rules;
|
|
114
|
+
// webpieces-disable no-any-unknown -- a validated rule entry is still opaque JSON in the option bag
|
|
115
|
+
const optionsOf = (name) => gate.isDisabled(workspaceRoot, name, true) ? null : (rules.get(name)?.options ?? null);
|
|
116
|
+
const deps = optionsOf(rules_config_1.RULE_NAMES.API_LIB_DEPENDENCIES);
|
|
117
|
+
const where = optionsOf(rules_config_1.RULE_NAMES.API_LIB_PATH);
|
|
118
|
+
const folder = optionsOf(rules_config_1.RULE_NAMES.FRAMEWORK_FOLDER);
|
|
119
|
+
return new TagTruthRules(deps === null ? null : TagTruthRules.dependenciesRule(deps), where === null ? null : new ApiLibPathRule(where['paths']), folder === null
|
|
120
|
+
? null
|
|
121
|
+
: new FrameworkFolderRule(folder['entries'].map(FrameworkFolder.fromEntry)));
|
|
122
|
+
}
|
|
123
|
+
// webpieces-disable no-function-outside-class -- private static reader of this class
|
|
124
|
+
static dependenciesRule(options) {
|
|
125
|
+
const clients = new Map();
|
|
126
|
+
for (const entry of options['apiClients'])
|
|
127
|
+
clients.set(entry.project, entry.packages);
|
|
128
|
+
return new ApiLibDependenciesRule(options['apiLibPackages'], clients);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.TagTruthRules = TagTruthRules;
|
|
132
|
+
/** What ONE project imports from production source, and the package name others import it by. */
|
|
133
|
+
class ProjectImports {
|
|
134
|
+
packageName;
|
|
135
|
+
prodPackages;
|
|
136
|
+
constructor(
|
|
137
|
+
/** The project's own `package.json` name, or null when it has none. */
|
|
138
|
+
packageName,
|
|
139
|
+
/** Bare package names its production (non-test) source imports. */
|
|
140
|
+
prodPackages) {
|
|
141
|
+
this.packageName = packageName;
|
|
142
|
+
this.prodPackages = prodPackages;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.ProjectImports = ProjectImports;
|
|
146
|
+
/** The pure check: every tag-truth problem in a graph whose framework sets and roles are resolved. */
|
|
147
|
+
class TagTruthValidator {
|
|
148
|
+
rules;
|
|
149
|
+
constructor(rules) {
|
|
150
|
+
this.rules = rules;
|
|
151
|
+
}
|
|
152
|
+
problems(graph, infos, imports) {
|
|
153
|
+
const problems = [];
|
|
154
|
+
for (const name of Object.keys(graph).sort()) {
|
|
155
|
+
const entry = graph[name];
|
|
156
|
+
const info = infos.get(name);
|
|
157
|
+
if (info === undefined || info.root === '' || info.root === '.')
|
|
158
|
+
continue;
|
|
159
|
+
const root = info.root.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
160
|
+
if (this.rules.apiLibDependencies !== null) {
|
|
161
|
+
problems.push(...this.dependencyProblems(name, entry, graph, imports, this.rules.apiLibDependencies));
|
|
162
|
+
}
|
|
163
|
+
if (this.rules.apiLibPath !== null) {
|
|
164
|
+
problems.push(...this.pathProblems(name, root, entry, this.rules.apiLibPath));
|
|
165
|
+
}
|
|
166
|
+
if (this.rules.frameworkFolder !== null) {
|
|
167
|
+
problems.push(...this.folderProblems(name, root, entry, this.rules.frameworkFolder));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return problems;
|
|
171
|
+
}
|
|
172
|
+
/** D2 — the workspace edges and the outside packages of ONE api-lib / api-client. */
|
|
173
|
+
dependencyProblems(name, entry, graph, imports, rule) {
|
|
174
|
+
const role = entry.role;
|
|
175
|
+
if (role === undefined || !API_ROLES.includes(role))
|
|
176
|
+
return [];
|
|
177
|
+
const rulePrefix = `${rules_config_1.RULE_NAMES.API_LIB_DEPENDENCIES}: '${name}' (role:${role})`;
|
|
178
|
+
const clientPackages = rule.apiClientPackages.get(name);
|
|
179
|
+
if (role === exports.API_CLIENT_ROLE && clientPackages === undefined) {
|
|
180
|
+
return [
|
|
181
|
+
`${rulePrefix} has no apiClients entry — what an api-client talks to is stated, never inferred. ` +
|
|
182
|
+
`Add { "project": "${name}", "packages": [<its SDK, inversify, …>] } to ` +
|
|
183
|
+
`${rules_config_1.RULE_NAMES.API_LIB_DEPENDENCIES}.apiClients in webpieces.config.json, or retag it role:api-lib ` +
|
|
184
|
+
'if it bundles no implementation.',
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
const allowed = [...rule.apiLibPackages, ...(clientPackages ?? [])];
|
|
188
|
+
const listName = role === exports.API_CLIENT_ROLE ? `apiLibPackages or its apiClients entry` : 'apiLibPackages';
|
|
189
|
+
const problems = [];
|
|
190
|
+
const workspaceNames = new Set();
|
|
191
|
+
for (const dep of entry.dependsOn) {
|
|
192
|
+
const depImports = imports.get(dep);
|
|
193
|
+
if (depImports?.packageName)
|
|
194
|
+
workspaceNames.add(depImports.packageName);
|
|
195
|
+
const depRole = graph[dep]?.role;
|
|
196
|
+
if (depRole === exports.API_LIB_ROLE)
|
|
197
|
+
continue;
|
|
198
|
+
if ((0, rules_config_1.matchesAnyGlob)(dep, allowed))
|
|
199
|
+
continue;
|
|
200
|
+
if (depImports?.packageName && (0, rules_config_1.matchesAnyGlob)(depImports.packageName, allowed))
|
|
201
|
+
continue;
|
|
202
|
+
problems.push(`${rulePrefix} must not depend on '${dep}' (role:${depRole ?? 'none'}) — an api library depends ` +
|
|
203
|
+
`only on role:api-lib projects and the packages ${listName} lists. Move what it needs into a ` +
|
|
204
|
+
`role:api-lib project, or add '${depImports?.packageName ?? dep}' to ${listName}.`);
|
|
205
|
+
}
|
|
206
|
+
const own = imports.get(name);
|
|
207
|
+
for (const pkg of [...(own?.prodPackages ?? [])].sort()) {
|
|
208
|
+
if (pkg === own?.packageName || workspaceNames.has(pkg) || this.isWorkspacePackage(pkg, imports))
|
|
209
|
+
continue;
|
|
210
|
+
if ((0, rules_config_1.matchesAnyGlob)(pkg, allowed))
|
|
211
|
+
continue;
|
|
212
|
+
problems.push(`${rulePrefix} imports '${pkg}', which ${listName} does not list — an api library's wire contract ` +
|
|
213
|
+
`must not drag an outside runtime into every consumer. Remove the import, or add '${pkg}' ` +
|
|
214
|
+
`to ${rules_config_1.RULE_NAMES.API_LIB_DEPENDENCIES}.${role === exports.API_CLIENT_ROLE ? `apiClients["${name}"].packages` : 'apiLibPackages'}.`);
|
|
215
|
+
}
|
|
216
|
+
return problems;
|
|
217
|
+
}
|
|
218
|
+
/** A package some workspace project publishes — its edge is judged as a workspace dependency above. */
|
|
219
|
+
isWorkspacePackage(pkg, imports) {
|
|
220
|
+
for (const each of imports.values())
|
|
221
|
+
if (each.packageName === pkg)
|
|
222
|
+
return true;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
/** D10 — an api project lives under the api globs, and everything there is an api project. */
|
|
226
|
+
pathProblems(name, root, entry, rule) {
|
|
227
|
+
const isApi = entry.role !== undefined && API_ROLES.includes(entry.role);
|
|
228
|
+
const inPath = (0, rules_config_1.matchesAnyGlob)(root, rule.paths);
|
|
229
|
+
const globs = `[${rule.paths.join(', ')}]`;
|
|
230
|
+
if (isApi && !inPath) {
|
|
231
|
+
return [
|
|
232
|
+
`${rules_config_1.RULE_NAMES.API_LIB_PATH}: '${name}' is role:${entry.role} but lives at '${root}', outside ` +
|
|
233
|
+
`${rules_config_1.RULE_NAMES.API_LIB_PATH}.paths ${globs} — move it under one of them ` +
|
|
234
|
+
'(nx g @nx/workspace:move), so every contract in the repo is found in one place.',
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
if (inPath && !isApi) {
|
|
238
|
+
return [
|
|
239
|
+
`${rules_config_1.RULE_NAMES.API_LIB_PATH}: '${name}' lives at '${root}', under ${rules_config_1.RULE_NAMES.API_LIB_PATH}.paths ` +
|
|
240
|
+
`${globs}, but is role:${entry.role ?? 'none'} — everything there is a contract. Retag it ` +
|
|
241
|
+
'role:api-lib (a contract and/or its DTOs) or role:api-client (a contract plus its SDK ' +
|
|
242
|
+
'adapter), or move it out of the api tree.',
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
/** D9 — the folder and the tags agree, both directions. */
|
|
248
|
+
folderProblems(name, root, entry, rule) {
|
|
249
|
+
if (entry.framework === undefined || entry.role === undefined)
|
|
250
|
+
return [];
|
|
251
|
+
const set = FrameworkFolder.canonical(entry.framework);
|
|
252
|
+
const role = entry.role;
|
|
253
|
+
const governing = rule.folders.find((folder) => (0, rules_config_1.matchesAnyGlob)(root, folder.paths));
|
|
254
|
+
const prefix = `${rules_config_1.RULE_NAMES.FRAMEWORK_FOLDER}: '${name}' at '${root}'`;
|
|
255
|
+
if (governing !== undefined) {
|
|
256
|
+
const problems = [];
|
|
257
|
+
const glob = governing.paths.join(', ');
|
|
258
|
+
if (!governing.frameworkSets.includes(set)) {
|
|
259
|
+
problems.push(`${prefix} carries framework set [${set}], but the folder ${glob} holds ` +
|
|
260
|
+
`[${governing.frameworkSets.join(' | ')}] — retag it to the folder's set, or move it to ` +
|
|
261
|
+
'the folder that holds its set.');
|
|
262
|
+
}
|
|
263
|
+
if (!governing.roles.includes(role)) {
|
|
264
|
+
problems.push(`${prefix} is role:${role}, but the folder ${glob} holds role ` +
|
|
265
|
+
`${governing.roles.map((each) => `role:${each}`).join(' | ')} — retag it, or move it.`);
|
|
266
|
+
}
|
|
267
|
+
return problems;
|
|
268
|
+
}
|
|
269
|
+
if (graph_metadata_1.APP_ROLES.includes(role) || role === 'bundle')
|
|
270
|
+
return [];
|
|
271
|
+
const homes = rule.folders.filter((folder) => folder.frameworkSets.includes(set) && folder.roles.includes(role));
|
|
272
|
+
if (homes.length === 0)
|
|
273
|
+
return [];
|
|
274
|
+
const globs = homes.map((folder) => folder.paths.join(', ')).join(' | ');
|
|
275
|
+
return [
|
|
276
|
+
`${prefix} is a role:${role} carrying [${set}], which ${rules_config_1.RULE_NAMES.FRAMEWORK_FOLDER} places under ` +
|
|
277
|
+
`${globs} — move it there (nx g @nx/workspace:move), so the folder tells a reader what runs where.`,
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.TagTruthValidator = TagTruthValidator;
|
|
282
|
+
/** The executor-facing entry point: read the config and the imports, check, and throw before any write. */
|
|
283
|
+
class TagTruthCheck {
|
|
284
|
+
assertTrue(graph, infos, workspaceRoot) {
|
|
285
|
+
const rules = TagTruthRules.fromConfig(workspaceRoot);
|
|
286
|
+
if (!rules.anyEnabled())
|
|
287
|
+
return;
|
|
288
|
+
const imports = rules.apiLibDependencies === null ? new Map() : this.importsOf(infos, workspaceRoot);
|
|
289
|
+
const problems = new TagTruthValidator(rules).problems(graph, infos, imports);
|
|
290
|
+
if (problems.length > 0)
|
|
291
|
+
throw new graph_metadata_1.MetadataValidationError(problems);
|
|
292
|
+
}
|
|
293
|
+
/** Every project's production imports and package name, read from its source and package.json. */
|
|
294
|
+
importsOf(infos, workspaceRoot) {
|
|
295
|
+
const scanner = new dep_usage_scanner_1.DepUsageScanner();
|
|
296
|
+
const found = new Map();
|
|
297
|
+
for (const info of infos.values()) {
|
|
298
|
+
if (info.root === '' || info.root === '.')
|
|
299
|
+
continue;
|
|
300
|
+
const abs = path.resolve(workspaceRoot, info.root);
|
|
301
|
+
found.set(info.name, new ProjectImports(this.packageNameOf(abs), scanner.scan(abs).prodPackages));
|
|
302
|
+
}
|
|
303
|
+
return found;
|
|
304
|
+
}
|
|
305
|
+
packageNameOf(projectDir) {
|
|
306
|
+
const file = path.join(projectDir, 'package.json');
|
|
307
|
+
if (!fs.existsSync(file))
|
|
308
|
+
return null;
|
|
309
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
310
|
+
try {
|
|
311
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
312
|
+
return typeof parsed.name === 'string' ? parsed.name : null;
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
const error = (0, toError_1.toError)(err);
|
|
316
|
+
throw new Error(`Failed to parse ${file} while reading the package name tag-truth checks against`, {
|
|
317
|
+
cause: error,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
exports.TagTruthCheck = TagTruthCheck;
|
|
323
|
+
//# sourceMappingURL=tag-truth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-truth.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/tag-truth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tag truth — the three GRAPH rules that make an nx `role:*` / `framework:*` tag true (#1064):\n *\n * api-lib-dependencies (D2) — a `role:api-lib` depends only on other `role:api-lib` projects plus a\n * stated list of outside packages; a `role:api-client` may also import\n * the packages its own config entry names (its SDK, inversify, …).\n * api-lib-path (D10) — every api-lib / api-client lives under the configured api globs, and\n * everything under them carries one of those two roles.\n * framework-folder (D9) — the folder a library lives in and its framework set + role agree,\n * both directions.\n *\n * They run beside `library-types-match-client` and `role-dependency` (graph-metadata.ts), in\n * `architecture:generate` and `validate-architecture-unchanged`, AFTER enrichGraph has resolved every\n * project's framework set and role — so a tag is read exactly one way. Every problem across every\n * project is collected and thrown as one MetadataValidationError, before anything is written.\n *\n * There is NO default (`.claude/rules/no-rule-defaults.md`): each rule's fields come from\n * webpieces.config.json, which must state them or fail to load. `TagTruthRules.none()` is what a unit\n * test or a caller that configured nothing asked for — not a default.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n ApiClientPackagesEntry,\n FrameworkFolderEntry,\n loadAndValidate,\n matchesAnyGlob,\n RULE_NAMES,\n RuleOptions,\n} from '@webpieces/rules-config';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport { APP_ROLES, MetadataValidationError } from './graph-metadata';\nimport { ProjectInfo } from './project-info';\nimport { RuleGate } from './rule-gate';\nimport { DepUsageScanner } from './dep-usage-scanner';\nimport { toError } from '../toError';\n\n/** The only field of a package.json this check reads. */\ntype RawPackageJson = { name?: string };\n\nexport const API_LIB_ROLE = 'api-lib';\nexport const API_CLIENT_ROLE = 'api-client';\nconst API_ROLES: readonly string[] = [API_LIB_ROLE, API_CLIENT_ROLE];\n\n/** `api-lib-dependencies`, resolved: what an api library may import from outside the api-lib set. */\nexport class ApiLibDependenciesRule {\n constructor(\n /** Package / project names (globs allowed) every api-lib and api-client may depend on. */\n public readonly apiLibPackages: readonly string[],\n /** `role:api-client` project → the extra packages it may depend on. */\n public readonly apiClientPackages: ReadonlyMap<string, readonly string[]>,\n ) {}\n}\n\n/** `api-lib-path`, resolved: the globs every api project — and only api projects — lives under. */\nexport class ApiLibPathRule {\n constructor(public readonly paths: readonly string[]) {}\n}\n\n/** One `framework-folder` entry with its framework sets canonicalised (`a+b`, tokens sorted). */\nexport class FrameworkFolder {\n constructor(\n public readonly paths: readonly string[],\n public readonly frameworkSets: readonly string[],\n public readonly roles: readonly string[],\n ) {}\n\n /** A framework set written in any order, canonicalised: `node+browser` → `browser+node`. */\n // webpieces-disable no-function-outside-class -- static helper of this class\n static canonical(frameworks: readonly string[]): string {\n return [...new Set(frameworks.map((each: string) => each.trim()).filter((each: string) => each !== ''))]\n .sort()\n .join('+');\n }\n\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromEntry(entry: FrameworkFolderEntry): FrameworkFolder {\n return new FrameworkFolder(\n entry.paths,\n entry.frameworkSets.map((set: string) => FrameworkFolder.canonical(set.split('+'))),\n entry.roles,\n );\n }\n}\n\n/** `framework-folder`, resolved. Entry ORDER matters: the first entry whose glob matches governs. */\nexport class FrameworkFolderRule {\n constructor(public readonly folders: readonly FrameworkFolder[]) {}\n}\n\n/**\n * The three rules' switches, read from webpieces.config.json once. A rule that is OFF (or time-boxed /\n * branch-scoped off) is `null`.\n */\nexport class TagTruthRules {\n constructor(\n public readonly apiLibDependencies: ApiLibDependenciesRule | null,\n public readonly apiLibPath: ApiLibPathRule | null,\n public readonly frameworkFolder: FrameworkFolderRule | null,\n ) {}\n\n /** Every rule off — what a caller that configured nothing asked for. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static none(): TagTruthRules {\n return new TagTruthRules(null, null, null);\n }\n\n anyEnabled(): boolean {\n return this.apiLibDependencies !== null || this.apiLibPath !== null || this.frameworkFolder !== null;\n }\n\n /** `mode: OFF` and both escape hatches come from RuleGate, so there is ONE reading of them. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromConfig(workspaceRoot: string): TagTruthRules {\n const gate = new RuleGate();\n const rules = loadAndValidate(workspaceRoot).resolved.rules;\n // webpieces-disable no-any-unknown -- a validated rule entry is still opaque JSON in the option bag\n const optionsOf = (name: string): RuleOptions | null =>\n gate.isDisabled(workspaceRoot, name, true) ? null : (rules.get(name)?.options ?? null);\n\n const deps = optionsOf(RULE_NAMES.API_LIB_DEPENDENCIES);\n const where = optionsOf(RULE_NAMES.API_LIB_PATH);\n const folder = optionsOf(RULE_NAMES.FRAMEWORK_FOLDER);\n return new TagTruthRules(\n deps === null ? null : TagTruthRules.dependenciesRule(deps),\n where === null ? null : new ApiLibPathRule(where['paths'] as string[]),\n folder === null\n ? null\n : new FrameworkFolderRule((folder['entries'] as FrameworkFolderEntry[]).map(FrameworkFolder.fromEntry)),\n );\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static dependenciesRule(options: RuleOptions): ApiLibDependenciesRule {\n const clients = new Map<string, readonly string[]>();\n for (const entry of options['apiClients'] as ApiClientPackagesEntry[]) clients.set(entry.project, entry.packages);\n return new ApiLibDependenciesRule(options['apiLibPackages'] as string[], clients);\n }\n}\n\n/** What ONE project imports from production source, and the package name others import it by. */\nexport class ProjectImports {\n constructor(\n /** The project's own `package.json` name, or null when it has none. */\n public readonly packageName: string | null,\n /** Bare package names its production (non-test) source imports. */\n public readonly prodPackages: ReadonlySet<string>,\n ) {}\n}\n\n/** The pure check: every tag-truth problem in a graph whose framework sets and roles are resolved. */\nexport class TagTruthValidator {\n constructor(private readonly rules: TagTruthRules) {}\n\n problems(\n graph: EnhancedGraph,\n infos: Map<string, ProjectInfo>,\n imports: ReadonlyMap<string, ProjectImports>,\n ): string[] {\n const problems: string[] = [];\n for (const name of Object.keys(graph).sort()) {\n const entry = graph[name];\n const info = infos.get(name);\n if (info === undefined || info.root === '' || info.root === '.') continue;\n const root = info.root.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n if (this.rules.apiLibDependencies !== null) {\n problems.push(...this.dependencyProblems(name, entry, graph, imports, this.rules.apiLibDependencies));\n }\n if (this.rules.apiLibPath !== null) {\n problems.push(...this.pathProblems(name, root, entry, this.rules.apiLibPath));\n }\n if (this.rules.frameworkFolder !== null) {\n problems.push(...this.folderProblems(name, root, entry, this.rules.frameworkFolder));\n }\n }\n return problems;\n }\n\n /** D2 — the workspace edges and the outside packages of ONE api-lib / api-client. */\n private dependencyProblems(\n name: string,\n entry: GraphEntry,\n graph: EnhancedGraph,\n imports: ReadonlyMap<string, ProjectImports>,\n rule: ApiLibDependenciesRule,\n ): string[] {\n const role = entry.role;\n if (role === undefined || !API_ROLES.includes(role)) return [];\n const rulePrefix = `${RULE_NAMES.API_LIB_DEPENDENCIES}: '${name}' (role:${role})`;\n const clientPackages = rule.apiClientPackages.get(name);\n if (role === API_CLIENT_ROLE && clientPackages === undefined) {\n return [\n `${rulePrefix} has no apiClients entry — what an api-client talks to is stated, never inferred. ` +\n `Add { \"project\": \"${name}\", \"packages\": [<its SDK, inversify, …>] } to ` +\n `${RULE_NAMES.API_LIB_DEPENDENCIES}.apiClients in webpieces.config.json, or retag it role:api-lib ` +\n 'if it bundles no implementation.',\n ];\n }\n const allowed = [...rule.apiLibPackages, ...(clientPackages ?? [])];\n const listName =\n role === API_CLIENT_ROLE ? `apiLibPackages or its apiClients entry` : 'apiLibPackages';\n const problems: string[] = [];\n const workspaceNames = new Set<string>();\n for (const dep of entry.dependsOn) {\n const depImports = imports.get(dep);\n if (depImports?.packageName) workspaceNames.add(depImports.packageName);\n const depRole = graph[dep]?.role;\n if (depRole === API_LIB_ROLE) continue;\n if (matchesAnyGlob(dep, allowed)) continue;\n if (depImports?.packageName && matchesAnyGlob(depImports.packageName, allowed)) continue;\n problems.push(\n `${rulePrefix} must not depend on '${dep}' (role:${depRole ?? 'none'}) — an api library depends ` +\n `only on role:api-lib projects and the packages ${listName} lists. Move what it needs into a ` +\n `role:api-lib project, or add '${depImports?.packageName ?? dep}' to ${listName}.`,\n );\n }\n const own = imports.get(name);\n for (const pkg of [...(own?.prodPackages ?? [])].sort()) {\n if (pkg === own?.packageName || workspaceNames.has(pkg) || this.isWorkspacePackage(pkg, imports)) continue;\n if (matchesAnyGlob(pkg, allowed)) continue;\n problems.push(\n `${rulePrefix} imports '${pkg}', which ${listName} does not list — an api library's wire contract ` +\n `must not drag an outside runtime into every consumer. Remove the import, or add '${pkg}' ` +\n `to ${RULE_NAMES.API_LIB_DEPENDENCIES}.${role === API_CLIENT_ROLE ? `apiClients[\"${name}\"].packages` : 'apiLibPackages'}.`,\n );\n }\n return problems;\n }\n\n /** A package some workspace project publishes — its edge is judged as a workspace dependency above. */\n private isWorkspacePackage(pkg: string, imports: ReadonlyMap<string, ProjectImports>): boolean {\n for (const each of imports.values()) if (each.packageName === pkg) return true;\n return false;\n }\n\n /** D10 — an api project lives under the api globs, and everything there is an api project. */\n private pathProblems(name: string, root: string, entry: GraphEntry, rule: ApiLibPathRule): string[] {\n const isApi = entry.role !== undefined && API_ROLES.includes(entry.role);\n const inPath = matchesAnyGlob(root, rule.paths);\n const globs = `[${rule.paths.join(', ')}]`;\n if (isApi && !inPath) {\n return [\n `${RULE_NAMES.API_LIB_PATH}: '${name}' is role:${entry.role} but lives at '${root}', outside ` +\n `${RULE_NAMES.API_LIB_PATH}.paths ${globs} — move it under one of them ` +\n '(nx g @nx/workspace:move), so every contract in the repo is found in one place.',\n ];\n }\n if (inPath && !isApi) {\n return [\n `${RULE_NAMES.API_LIB_PATH}: '${name}' lives at '${root}', under ${RULE_NAMES.API_LIB_PATH}.paths ` +\n `${globs}, but is role:${entry.role ?? 'none'} — everything there is a contract. Retag it ` +\n 'role:api-lib (a contract and/or its DTOs) or role:api-client (a contract plus its SDK ' +\n 'adapter), or move it out of the api tree.',\n ];\n }\n return [];\n }\n\n /** D9 — the folder and the tags agree, both directions. */\n private folderProblems(name: string, root: string, entry: GraphEntry, rule: FrameworkFolderRule): string[] {\n if (entry.framework === undefined || entry.role === undefined) return [];\n const set = FrameworkFolder.canonical(entry.framework);\n const role = entry.role;\n const governing = rule.folders.find((folder: FrameworkFolder) => matchesAnyGlob(root, folder.paths));\n const prefix = `${RULE_NAMES.FRAMEWORK_FOLDER}: '${name}' at '${root}'`;\n if (governing !== undefined) {\n const problems: string[] = [];\n const glob = governing.paths.join(', ');\n if (!governing.frameworkSets.includes(set)) {\n problems.push(\n `${prefix} carries framework set [${set}], but the folder ${glob} holds ` +\n `[${governing.frameworkSets.join(' | ')}] — retag it to the folder's set, or move it to ` +\n 'the folder that holds its set.',\n );\n }\n if (!governing.roles.includes(role)) {\n problems.push(\n `${prefix} is role:${role}, but the folder ${glob} holds role ` +\n `${governing.roles.map((each: string) => `role:${each}`).join(' | ')} — retag it, or move it.`,\n );\n }\n return problems;\n }\n if (APP_ROLES.includes(role) || role === 'bundle') return [];\n const homes = rule.folders.filter(\n (folder: FrameworkFolder) => folder.frameworkSets.includes(set) && folder.roles.includes(role),\n );\n if (homes.length === 0) return [];\n const globs = homes.map((folder: FrameworkFolder) => folder.paths.join(', ')).join(' | ');\n return [\n `${prefix} is a role:${role} carrying [${set}], which ${RULE_NAMES.FRAMEWORK_FOLDER} places under ` +\n `${globs} — move it there (nx g @nx/workspace:move), so the folder tells a reader what runs where.`,\n ];\n }\n}\n\n/** The executor-facing entry point: read the config and the imports, check, and throw before any write. */\nexport class TagTruthCheck {\n assertTrue(graph: EnhancedGraph, infos: Map<string, ProjectInfo>, workspaceRoot: string): void {\n const rules = TagTruthRules.fromConfig(workspaceRoot);\n if (!rules.anyEnabled()) return;\n const imports = rules.apiLibDependencies === null ? new Map<string, ProjectImports>() : this.importsOf(infos, workspaceRoot);\n const problems = new TagTruthValidator(rules).problems(graph, infos, imports);\n if (problems.length > 0) throw new MetadataValidationError(problems);\n }\n\n /** Every project's production imports and package name, read from its source and package.json. */\n importsOf(infos: Map<string, ProjectInfo>, workspaceRoot: string): Map<string, ProjectImports> {\n const scanner = new DepUsageScanner();\n const found = new Map<string, ProjectImports>();\n for (const info of infos.values()) {\n if (info.root === '' || info.root === '.') continue;\n const abs = path.resolve(workspaceRoot, info.root);\n found.set(info.name, new ProjectImports(this.packageNameOf(abs), scanner.scan(abs).prodPackages));\n }\n return found;\n }\n\n private packageNameOf(projectDir: string): string | null {\n const file = path.join(projectDir, 'package.json');\n if (!fs.existsSync(file)) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed = JSON.parse(fs.readFileSync(file, 'utf-8')) as RawPackageJson;\n return typeof parsed.name === 'string' ? parsed.name : null;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to parse ${file} while reading the package name tag-truth checks against`, {\n cause: error,\n });\n }\n }\n}\n"]}
|