@webpieces/nx-webpieces-rules 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/executors.json +124 -0
- package/package.json +36 -0
- package/src/executor-result.ts +7 -0
- package/src/executors/generate/executor.ts +61 -0
- package/src/executors/generate/schema.json +14 -0
- package/src/executors/help/executor.ts +63 -0
- package/src/executors/help/schema.json +7 -0
- package/src/executors/validate-architecture-unchanged/executor.ts +253 -0
- package/src/executors/validate-architecture-unchanged/schema.json +14 -0
- package/src/executors/validate-catch-error-pattern/executor.ts +11 -0
- package/src/executors/validate-catch-error-pattern/schema.json +24 -0
- package/src/executors/validate-code/executor.ts +11 -0
- package/src/executors/validate-code/schema.json +287 -0
- package/src/executors/validate-dtos/executor.ts +11 -0
- package/src/executors/validate-dtos/schema.json +33 -0
- package/src/executors/validate-eslint-sync/executor.ts +87 -0
- package/src/executors/validate-eslint-sync/schema.json +7 -0
- package/src/executors/validate-modified-files/executor.ts +11 -0
- package/src/executors/validate-modified-files/schema.json +25 -0
- package/src/executors/validate-modified-methods/executor.ts +11 -0
- package/src/executors/validate-modified-methods/schema.json +25 -0
- package/src/executors/validate-new-methods/executor.ts +11 -0
- package/src/executors/validate-new-methods/schema.json +25 -0
- package/src/executors/validate-no-any-unknown/executor.ts +11 -0
- package/src/executors/validate-no-any-unknown/schema.json +24 -0
- package/src/executors/validate-no-architecture-cycles/executor.ts +63 -0
- package/src/executors/validate-no-architecture-cycles/schema.json +8 -0
- package/src/executors/validate-no-destructure/executor.ts +11 -0
- package/src/executors/validate-no-destructure/schema.json +24 -0
- package/src/executors/validate-no-direct-api-resolver/executor.ts +11 -0
- package/src/executors/validate-no-direct-api-resolver/schema.json +29 -0
- package/src/executors/validate-no-implicit-any/executor.ts +11 -0
- package/src/executors/validate-no-implicit-any/schema.json +24 -0
- package/src/executors/validate-no-inline-types/executor.ts +11 -0
- package/src/executors/validate-no-inline-types/schema.json +24 -0
- package/src/executors/validate-no-skiplevel-deps/executor.ts +274 -0
- package/src/executors/validate-no-skiplevel-deps/schema.json +8 -0
- package/src/executors/validate-no-unmanaged-exceptions/executor.ts +11 -0
- package/src/executors/validate-no-unmanaged-exceptions/schema.json +24 -0
- package/src/executors/validate-packagejson/executor.ts +76 -0
- package/src/executors/validate-packagejson/schema.json +8 -0
- package/src/executors/validate-prisma-converters/executor.ts +11 -0
- package/src/executors/validate-prisma-converters/schema.json +38 -0
- package/src/executors/validate-return-types/executor.ts +11 -0
- package/src/executors/validate-return-types/schema.json +24 -0
- package/src/executors/validate-ts-in-src/executor.ts +283 -0
- package/src/executors/validate-ts-in-src/schema.json +25 -0
- package/src/executors/validate-versions-locked/executor.ts +376 -0
- package/src/executors/validate-versions-locked/schema.json +8 -0
- package/src/executors/visualize/executor.ts +65 -0
- package/src/executors/visualize/schema.json +14 -0
- package/src/index.ts +9 -0
- package/src/lib/graph-comparator.ts +154 -0
- package/src/lib/graph-generator.ts +97 -0
- package/src/lib/graph-loader.ts +119 -0
- package/src/lib/graph-sorter.ts +137 -0
- package/src/lib/graph-visualizer.ts +253 -0
- package/src/lib/package-validator.ts +184 -0
- package/src/plugin.ts +666 -0
- package/src/toError.ts +36 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Nx Inference Plugin for @webpieces/nx-webpieces-rules
|
|
3
|
+
*
|
|
4
|
+
* This plugin automatically creates targets for:
|
|
5
|
+
* 1. Workspace-level architecture validation (generate, visualize, validate-*)
|
|
6
|
+
* 2. Per-project circular dependency checking
|
|
7
|
+
*
|
|
8
|
+
* Install with: nx add @webpieces/nx-webpieces-rules
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* Add to nx.json plugins array:
|
|
12
|
+
* {
|
|
13
|
+
* "plugins": ["@webpieces/nx-webpieces-rules"]
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* Then all targets appear automatically without manual project.json configuration.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { dirname, join } from 'path';
|
|
20
|
+
import { existsSync } from 'fs';
|
|
21
|
+
import type {
|
|
22
|
+
CreateNodesV2,
|
|
23
|
+
CreateNodesContextV2,
|
|
24
|
+
CreateNodesResultV2,
|
|
25
|
+
CreateNodesResult,
|
|
26
|
+
TargetConfiguration,
|
|
27
|
+
} from '@nx/devkit';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Circular dependency checking options
|
|
31
|
+
*/
|
|
32
|
+
export interface CircularDepsOptions {
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
targetName?: string;
|
|
35
|
+
excludePatterns?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validation options for architecture checks
|
|
40
|
+
*/
|
|
41
|
+
export interface ValidationOptions {
|
|
42
|
+
noCycles?: boolean;
|
|
43
|
+
noSkipLevelDeps?: boolean;
|
|
44
|
+
architectureUnchanged?: boolean;
|
|
45
|
+
validatePackageJson?: boolean;
|
|
46
|
+
validateNewMethods?: boolean;
|
|
47
|
+
validateModifiedMethods?: boolean;
|
|
48
|
+
validateModifiedFiles?: boolean;
|
|
49
|
+
validateVersionsLocked?: boolean;
|
|
50
|
+
validateTsInSrc?: boolean;
|
|
51
|
+
newMethodsMaxLines?: number;
|
|
52
|
+
modifiedAndNewMethodsMaxLines?: number;
|
|
53
|
+
modifiedFilesMaxLines?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Validation mode for method/file size limits:
|
|
56
|
+
* - STRICT: All limits enforced, disable comments ignored
|
|
57
|
+
* - NORMAL: Limits enforced, disable comments with dates work
|
|
58
|
+
* - OFF: Skip size validations entirely (for fast iteration)
|
|
59
|
+
*/
|
|
60
|
+
validationMode?: 'STRICT' | 'NORMAL' | 'OFF';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Feature flags for workspace targets
|
|
65
|
+
*/
|
|
66
|
+
export interface FeatureOptions {
|
|
67
|
+
generate?: boolean;
|
|
68
|
+
visualize?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Workspace-level configuration options
|
|
73
|
+
*/
|
|
74
|
+
export interface WorkspaceOptions {
|
|
75
|
+
enabled?: boolean;
|
|
76
|
+
targetPrefix?: string;
|
|
77
|
+
graphPath?: string;
|
|
78
|
+
validations?: ValidationOptions;
|
|
79
|
+
features?: FeatureOptions;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Configuration for @webpieces/nx-webpieces-rules Nx plugin
|
|
84
|
+
*/
|
|
85
|
+
export interface ArchitecturePluginOptions {
|
|
86
|
+
circularDeps?: CircularDepsOptions;
|
|
87
|
+
workspace?: WorkspaceOptions;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DEFAULT_OPTIONS: Required<ArchitecturePluginOptions> = {
|
|
91
|
+
circularDeps: {
|
|
92
|
+
enabled: true,
|
|
93
|
+
targetName: 'validate-no-file-import-cycles',
|
|
94
|
+
excludePatterns: [],
|
|
95
|
+
},
|
|
96
|
+
workspace: {
|
|
97
|
+
enabled: true,
|
|
98
|
+
targetPrefix: 'arch:',
|
|
99
|
+
graphPath: 'architecture/dependencies.json',
|
|
100
|
+
validations: {
|
|
101
|
+
noCycles: true,
|
|
102
|
+
noSkipLevelDeps: true,
|
|
103
|
+
architectureUnchanged: true,
|
|
104
|
+
validatePackageJson: true,
|
|
105
|
+
validateNewMethods: true,
|
|
106
|
+
validateModifiedMethods: true,
|
|
107
|
+
validateModifiedFiles: true,
|
|
108
|
+
validateVersionsLocked: true,
|
|
109
|
+
validateTsInSrc: true,
|
|
110
|
+
newMethodsMaxLines: 30,
|
|
111
|
+
modifiedAndNewMethodsMaxLines: 80,
|
|
112
|
+
modifiedFilesMaxLines: 900,
|
|
113
|
+
validationMode: 'NORMAL',
|
|
114
|
+
},
|
|
115
|
+
features: {
|
|
116
|
+
generate: true,
|
|
117
|
+
visualize: true,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function normalizeOptions(
|
|
123
|
+
options: ArchitecturePluginOptions | undefined,
|
|
124
|
+
): Required<ArchitecturePluginOptions> {
|
|
125
|
+
const circularDeps = {
|
|
126
|
+
...DEFAULT_OPTIONS.circularDeps,
|
|
127
|
+
...options?.circularDeps,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const workspace = {
|
|
131
|
+
...DEFAULT_OPTIONS.workspace,
|
|
132
|
+
...options?.workspace,
|
|
133
|
+
validations: {
|
|
134
|
+
...DEFAULT_OPTIONS.workspace.validations,
|
|
135
|
+
...options?.workspace?.validations,
|
|
136
|
+
},
|
|
137
|
+
features: {
|
|
138
|
+
...DEFAULT_OPTIONS.workspace.features,
|
|
139
|
+
...options?.workspace?.features,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
circularDeps,
|
|
145
|
+
workspace,
|
|
146
|
+
} as Required<ArchitecturePluginOptions>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function createNodesFunction(
|
|
150
|
+
projectFiles: readonly string[],
|
|
151
|
+
options: ArchitecturePluginOptions | undefined,
|
|
152
|
+
context: CreateNodesContextV2,
|
|
153
|
+
): Promise<CreateNodesResultV2> {
|
|
154
|
+
const opts = normalizeOptions(options);
|
|
155
|
+
const results: CreateNodesResultV2 = [];
|
|
156
|
+
|
|
157
|
+
// Add workspace-level architecture targets
|
|
158
|
+
addArchitectureProject(results, projectFiles, opts, context);
|
|
159
|
+
|
|
160
|
+
// Add per-project targets (circular-deps, ci)
|
|
161
|
+
addPerProjectTargets(results, projectFiles, opts, context);
|
|
162
|
+
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function addArchitectureProject(
|
|
167
|
+
results: CreateNodesResultV2,
|
|
168
|
+
projectFiles: readonly string[],
|
|
169
|
+
opts: Required<ArchitecturePluginOptions>,
|
|
170
|
+
context: CreateNodesContextV2,
|
|
171
|
+
): void {
|
|
172
|
+
if (!opts.workspace.enabled) return;
|
|
173
|
+
|
|
174
|
+
const archDirPath = join(context.workspaceRoot, 'architecture');
|
|
175
|
+
if (!existsSync(archDirPath)) return;
|
|
176
|
+
|
|
177
|
+
const workspaceTargets = createWorkspaceTargetsWithoutPrefix(opts);
|
|
178
|
+
if (Object.keys(workspaceTargets).length === 0) return;
|
|
179
|
+
|
|
180
|
+
const result: CreateNodesResult = {
|
|
181
|
+
projects: {
|
|
182
|
+
architecture: {
|
|
183
|
+
name: 'architecture',
|
|
184
|
+
root: 'architecture',
|
|
185
|
+
targets: workspaceTargets,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const firstProjectFile = projectFiles[0];
|
|
191
|
+
if (firstProjectFile) {
|
|
192
|
+
results.push([firstProjectFile, result] as const);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function addPerProjectTargets(
|
|
197
|
+
results: CreateNodesResultV2,
|
|
198
|
+
projectFiles: readonly string[],
|
|
199
|
+
opts: Required<ArchitecturePluginOptions>,
|
|
200
|
+
context: CreateNodesContextV2,
|
|
201
|
+
): void {
|
|
202
|
+
// Track processed project roots to avoid duplicates when both files exist
|
|
203
|
+
const processedRoots = new Set<string>();
|
|
204
|
+
|
|
205
|
+
for (const projectFile of projectFiles) {
|
|
206
|
+
const isProjectJson = projectFile.endsWith('project.json');
|
|
207
|
+
const isPackageJson = projectFile.endsWith('package.json');
|
|
208
|
+
|
|
209
|
+
if (!isProjectJson && !isPackageJson) continue;
|
|
210
|
+
|
|
211
|
+
const projectRoot = dirname(projectFile);
|
|
212
|
+
|
|
213
|
+
// Skip root (workspace manifest, not a project)
|
|
214
|
+
if (projectRoot === '.') continue;
|
|
215
|
+
|
|
216
|
+
// Skip if we've already processed this project root
|
|
217
|
+
if (processedRoots.has(projectRoot)) continue;
|
|
218
|
+
|
|
219
|
+
// For package.json, skip if project.json also exists in same directory
|
|
220
|
+
// (prefer project.json - it will be processed separately)
|
|
221
|
+
if (isPackageJson) {
|
|
222
|
+
const projectJsonPath = join(context.workspaceRoot, projectRoot, 'project.json');
|
|
223
|
+
if (existsSync(projectJsonPath)) continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
processedRoots.add(projectRoot);
|
|
227
|
+
|
|
228
|
+
const targets: Record<string, TargetConfiguration> = {};
|
|
229
|
+
|
|
230
|
+
// Add circular-deps target ONLY for project.json projects
|
|
231
|
+
// (package.json-only projects may not have TypeScript source)
|
|
232
|
+
if (isProjectJson && opts.circularDeps.enabled) {
|
|
233
|
+
if (!isExcluded(projectRoot, opts.circularDeps.excludePatterns!)) {
|
|
234
|
+
const targetName = opts.circularDeps.targetName!;
|
|
235
|
+
targets[targetName] = createCircularDepsTarget(projectRoot, targetName);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Add ci target to ALL projects (both project.json and package.json)
|
|
240
|
+
targets['ci'] = createCiTarget();
|
|
241
|
+
|
|
242
|
+
if (Object.keys(targets).length === 0) continue;
|
|
243
|
+
|
|
244
|
+
const result: CreateNodesResult = {
|
|
245
|
+
projects: {
|
|
246
|
+
[projectRoot]: {
|
|
247
|
+
targets,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
results.push([projectFile, result] as const);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Nx V2 Inference Plugin
|
|
258
|
+
* Matches project.json and package.json files to create targets
|
|
259
|
+
*/
|
|
260
|
+
export const createNodesV2: CreateNodesV2<ArchitecturePluginOptions> = [
|
|
261
|
+
// Pattern to match project.json and package.json files
|
|
262
|
+
'**/{project,package}.json',
|
|
263
|
+
|
|
264
|
+
// Inference function
|
|
265
|
+
createNodesFunction,
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Build list of enabled validation target names for validate-complete dependency chain
|
|
270
|
+
*/
|
|
271
|
+
function buildValidationTargetsList(
|
|
272
|
+
validations: Required<ArchitecturePluginOptions>['workspace']['validations'],
|
|
273
|
+
): string[] {
|
|
274
|
+
const targets: string[] = [];
|
|
275
|
+
if (validations!.noCycles) targets.push('validate-no-architecture-cycles');
|
|
276
|
+
if (validations!.architectureUnchanged) targets.push('validate-architecture-unchanged');
|
|
277
|
+
if (validations!.noSkipLevelDeps) targets.push('validate-no-skiplevel-deps');
|
|
278
|
+
if (validations!.validatePackageJson) targets.push('validate-packagejson');
|
|
279
|
+
// Use combined validate-code instead of 3 separate targets
|
|
280
|
+
if (
|
|
281
|
+
validations!.validateNewMethods ||
|
|
282
|
+
validations!.validateModifiedMethods ||
|
|
283
|
+
validations!.validateModifiedFiles
|
|
284
|
+
) {
|
|
285
|
+
targets.push('validate-code');
|
|
286
|
+
}
|
|
287
|
+
if (validations!.validateVersionsLocked) targets.push('validate-versions-locked');
|
|
288
|
+
if (validations!.validateTsInSrc) targets.push('validate-ts-in-src');
|
|
289
|
+
return targets;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create workspace-level architecture validation targets WITHOUT prefix
|
|
294
|
+
* Used for virtual 'architecture' project
|
|
295
|
+
*/
|
|
296
|
+
function createWorkspaceTargetsWithoutPrefix(
|
|
297
|
+
opts: Required<ArchitecturePluginOptions>,
|
|
298
|
+
): Record<string, TargetConfiguration> {
|
|
299
|
+
const targets: Record<string, TargetConfiguration> = {};
|
|
300
|
+
const graphPath = opts.workspace.graphPath!;
|
|
301
|
+
const validations = opts.workspace.validations!;
|
|
302
|
+
|
|
303
|
+
// Add help target (always available)
|
|
304
|
+
targets['help'] = createHelpTarget();
|
|
305
|
+
|
|
306
|
+
if (opts.workspace.features!.generate) {
|
|
307
|
+
targets['generate'] = createGenerateTarget(graphPath);
|
|
308
|
+
}
|
|
309
|
+
if (opts.workspace.features!.visualize) {
|
|
310
|
+
targets['visualize'] = createVisualizeTargetWithoutPrefix(graphPath);
|
|
311
|
+
}
|
|
312
|
+
if (validations.noCycles) {
|
|
313
|
+
targets['validate-no-architecture-cycles'] = createValidateNoCyclesTarget();
|
|
314
|
+
}
|
|
315
|
+
if (validations.architectureUnchanged) {
|
|
316
|
+
targets['validate-architecture-unchanged'] = createValidateUnchangedTarget(graphPath);
|
|
317
|
+
}
|
|
318
|
+
if (validations.noSkipLevelDeps) {
|
|
319
|
+
targets['validate-no-skiplevel-deps'] = createValidateNoSkipLevelTarget();
|
|
320
|
+
}
|
|
321
|
+
if (validations.validatePackageJson) {
|
|
322
|
+
targets['validate-packagejson'] = createValidatePackageJsonTarget();
|
|
323
|
+
}
|
|
324
|
+
// Use combined validate-code instead of 3 separate targets
|
|
325
|
+
// Options come from webpieces.config.json at the workspace root
|
|
326
|
+
// (loaded via @webpieces/rules-config; same source of truth as @webpieces/ai-hook-rules)
|
|
327
|
+
if (
|
|
328
|
+
validations.validateNewMethods ||
|
|
329
|
+
validations.validateModifiedMethods ||
|
|
330
|
+
validations.validateModifiedFiles
|
|
331
|
+
) {
|
|
332
|
+
targets['validate-code'] = createValidateCodeTarget();
|
|
333
|
+
}
|
|
334
|
+
if (validations.validateVersionsLocked) {
|
|
335
|
+
targets['validate-versions-locked'] = createValidateVersionsLockedTarget();
|
|
336
|
+
}
|
|
337
|
+
if (validations.validateTsInSrc) {
|
|
338
|
+
targets['validate-ts-in-src'] = createValidateTsInSrcTarget();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Add validate-complete target that runs all enabled validations
|
|
342
|
+
const validationTargets = buildValidationTargetsList(validations);
|
|
343
|
+
if (validationTargets.length > 0) {
|
|
344
|
+
targets['validate-complete'] = createValidateCompleteTarget(validationTargets);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return targets;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Create workspace-level architecture validation targets (DEPRECATED - keeping for backward compat)
|
|
352
|
+
* Used when root project.json exists (old style with '.' project)
|
|
353
|
+
*/
|
|
354
|
+
function createWorkspaceTargets(
|
|
355
|
+
opts: Required<ArchitecturePluginOptions>,
|
|
356
|
+
): Record<string, TargetConfiguration> {
|
|
357
|
+
const targets: Record<string, TargetConfiguration> = {};
|
|
358
|
+
const prefix = opts.workspace.targetPrefix!;
|
|
359
|
+
const graphPath = opts.workspace.graphPath!;
|
|
360
|
+
|
|
361
|
+
// Add help target (always available)
|
|
362
|
+
targets[`${prefix}help`] = createHelpTarget();
|
|
363
|
+
|
|
364
|
+
if (opts.workspace.features!.generate) {
|
|
365
|
+
targets[`${prefix}generate`] = createGenerateTarget(graphPath);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (opts.workspace.features!.visualize) {
|
|
369
|
+
targets[`${prefix}visualize`] = createVisualizeTarget(prefix, graphPath);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (opts.workspace.validations!.noCycles) {
|
|
373
|
+
targets[`${prefix}validate-no-architecture-cycles`] = createValidateNoCyclesTarget();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (opts.workspace.validations!.architectureUnchanged) {
|
|
377
|
+
targets[`${prefix}validate-architecture-unchanged`] =
|
|
378
|
+
createValidateUnchangedTarget(graphPath);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (opts.workspace.validations!.noSkipLevelDeps) {
|
|
382
|
+
targets[`${prefix}validate-no-skiplevel-deps`] = createValidateNoSkipLevelTarget();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (opts.workspace.validations!.validatePackageJson) {
|
|
386
|
+
targets[`${prefix}validate-packagejson`] = createValidatePackageJsonTarget();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Use combined validate-code instead of 3 separate targets
|
|
390
|
+
// Options come from webpieces.config.json at the workspace root
|
|
391
|
+
// (loaded via @webpieces/rules-config; same source of truth as @webpieces/ai-hook-rules)
|
|
392
|
+
if (
|
|
393
|
+
opts.workspace.validations!.validateNewMethods ||
|
|
394
|
+
opts.workspace.validations!.validateModifiedMethods ||
|
|
395
|
+
opts.workspace.validations!.validateModifiedFiles
|
|
396
|
+
) {
|
|
397
|
+
targets[`${prefix}validate-code`] = createValidateCodeTarget();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return targets;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function createGenerateTarget(graphPath: string): TargetConfiguration {
|
|
404
|
+
return {
|
|
405
|
+
executor: '@webpieces/nx-webpieces-rules:generate',
|
|
406
|
+
cache: false,
|
|
407
|
+
outputs: ['{workspaceRoot}/architecture/dependencies.json'],
|
|
408
|
+
options: { graphPath },
|
|
409
|
+
metadata: {
|
|
410
|
+
technologies: ['nx'],
|
|
411
|
+
description: 'Generate the architecture dependency graph from project.json files',
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function createVisualizeTargetWithoutPrefix(graphPath: string): TargetConfiguration {
|
|
417
|
+
return {
|
|
418
|
+
executor: '@webpieces/nx-webpieces-rules:visualize',
|
|
419
|
+
dependsOn: ['generate'],
|
|
420
|
+
options: { graphPath },
|
|
421
|
+
metadata: {
|
|
422
|
+
technologies: ['nx'],
|
|
423
|
+
description: 'Generate visual representations of the architecture graph',
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function createVisualizeTarget(prefix: string, graphPath: string): TargetConfiguration {
|
|
429
|
+
return {
|
|
430
|
+
executor: '@webpieces/nx-webpieces-rules:visualize',
|
|
431
|
+
dependsOn: [`${prefix}generate`],
|
|
432
|
+
options: { graphPath },
|
|
433
|
+
metadata: {
|
|
434
|
+
technologies: ['nx'],
|
|
435
|
+
description: 'Generate visual representations of the architecture graph',
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function createValidateNoCyclesTarget(): TargetConfiguration {
|
|
441
|
+
return {
|
|
442
|
+
executor: '@webpieces/nx-webpieces-rules:validate-no-architecture-cycles',
|
|
443
|
+
cache: true,
|
|
444
|
+
inputs: ['{workspaceRoot}/**/project.json', '{workspaceRoot}/architecture/dependencies.json'],
|
|
445
|
+
metadata: {
|
|
446
|
+
technologies: ['nx'],
|
|
447
|
+
description: 'Validate the architecture has no circular project dependencies',
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function createValidateUnchangedTarget(graphPath: string): TargetConfiguration {
|
|
453
|
+
return {
|
|
454
|
+
executor: '@webpieces/nx-webpieces-rules:validate-architecture-unchanged',
|
|
455
|
+
cache: false,
|
|
456
|
+
inputs: ['default', '{workspaceRoot}/architecture/dependencies.json'],
|
|
457
|
+
options: { graphPath },
|
|
458
|
+
metadata: {
|
|
459
|
+
technologies: ['nx'],
|
|
460
|
+
description: 'Validate the architecture matches the saved blessed graph',
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function createValidateNoSkipLevelTarget(): TargetConfiguration {
|
|
466
|
+
return {
|
|
467
|
+
executor: '@webpieces/nx-webpieces-rules:validate-no-skiplevel-deps',
|
|
468
|
+
cache: true,
|
|
469
|
+
inputs: ['{workspaceRoot}/**/project.json', '{workspaceRoot}/architecture/dependencies.json'],
|
|
470
|
+
metadata: {
|
|
471
|
+
technologies: ['nx'],
|
|
472
|
+
description: 'Validate no project has redundant transitive dependencies',
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function createValidatePackageJsonTarget(): TargetConfiguration {
|
|
478
|
+
return {
|
|
479
|
+
executor: '@webpieces/nx-webpieces-rules:validate-packagejson',
|
|
480
|
+
cache: true,
|
|
481
|
+
inputs: ['{workspaceRoot}/**/project.json', '{workspaceRoot}/**/package.json'],
|
|
482
|
+
metadata: {
|
|
483
|
+
technologies: ['nx'],
|
|
484
|
+
description: 'Validate package.json dependencies match project.json build dependencies',
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function createValidateNewMethodsTarget(
|
|
490
|
+
maxLines: number,
|
|
491
|
+
mode: 'STRICT' | 'NORMAL' | 'OFF',
|
|
492
|
+
): TargetConfiguration {
|
|
493
|
+
return {
|
|
494
|
+
executor: '@webpieces/nx-webpieces-rules:validate-new-methods',
|
|
495
|
+
cache: false, // Don't cache - depends on git state
|
|
496
|
+
inputs: ['default'],
|
|
497
|
+
options: { max: maxLines, mode },
|
|
498
|
+
metadata: {
|
|
499
|
+
technologies: ['nx'],
|
|
500
|
+
description: `Validate new methods do not exceed ${maxLines} lines (only runs in affected mode)`,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function createValidateModifiedMethodsTarget(
|
|
506
|
+
maxLines: number,
|
|
507
|
+
mode: 'STRICT' | 'NORMAL' | 'OFF',
|
|
508
|
+
): TargetConfiguration {
|
|
509
|
+
return {
|
|
510
|
+
executor: '@webpieces/nx-webpieces-rules:validate-modified-methods',
|
|
511
|
+
cache: false, // Don't cache - depends on git state
|
|
512
|
+
inputs: ['default'],
|
|
513
|
+
options: { max: maxLines, mode },
|
|
514
|
+
metadata: {
|
|
515
|
+
technologies: ['nx'],
|
|
516
|
+
description: `Validate new and modified methods do not exceed ${maxLines} lines (encourages gradual cleanup)`,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function createValidateModifiedFilesTarget(
|
|
522
|
+
maxLines: number,
|
|
523
|
+
mode: 'STRICT' | 'NORMAL' | 'OFF',
|
|
524
|
+
): TargetConfiguration {
|
|
525
|
+
return {
|
|
526
|
+
executor: '@webpieces/nx-webpieces-rules:validate-modified-files',
|
|
527
|
+
cache: false, // Don't cache - depends on git state
|
|
528
|
+
inputs: ['default'],
|
|
529
|
+
options: { max: maxLines, mode },
|
|
530
|
+
metadata: {
|
|
531
|
+
technologies: ['nx'],
|
|
532
|
+
description: `Validate modified files do not exceed ${maxLines} lines (encourages keeping files small)`,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Create combined validate-code target
|
|
539
|
+
* Options come from webpieces.config.json at the workspace root
|
|
540
|
+
* (loaded by the executor via @webpieces/rules-config — same source of truth as @webpieces/ai-hook-rules)
|
|
541
|
+
*/
|
|
542
|
+
function createValidateCodeTarget(): TargetConfiguration {
|
|
543
|
+
return {
|
|
544
|
+
executor: '@webpieces/nx-webpieces-rules:validate-code',
|
|
545
|
+
cache: false, // Don't cache - depends on git state
|
|
546
|
+
inputs: ['default', '{workspaceRoot}/webpieces.config.json'],
|
|
547
|
+
// No options here - they come from webpieces.config.json at runtime
|
|
548
|
+
metadata: {
|
|
549
|
+
technologies: ['nx'],
|
|
550
|
+
description: 'Combined validation for new methods, modified methods, and file sizes',
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function createValidateVersionsLockedTarget(): TargetConfiguration {
|
|
556
|
+
return {
|
|
557
|
+
executor: '@webpieces/nx-webpieces-rules:validate-versions-locked',
|
|
558
|
+
cache: true,
|
|
559
|
+
inputs: ['{workspaceRoot}/**/package.json'],
|
|
560
|
+
metadata: {
|
|
561
|
+
technologies: ['nx'],
|
|
562
|
+
description:
|
|
563
|
+
'Validate package.json versions are locked (no semver ranges) and consistent across projects',
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function createValidateTsInSrcTarget(): TargetConfiguration {
|
|
569
|
+
return {
|
|
570
|
+
executor: '@webpieces/nx-webpieces-rules:validate-ts-in-src',
|
|
571
|
+
cache: false,
|
|
572
|
+
inputs: ['default', '{workspaceRoot}/webpieces.config.json'],
|
|
573
|
+
metadata: {
|
|
574
|
+
technologies: ['nx'],
|
|
575
|
+
description: 'Validate all .ts files in projects are inside the src/ directory',
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function createValidateCompleteTarget(validationTargets: string[]): TargetConfiguration {
|
|
581
|
+
return {
|
|
582
|
+
executor: 'nx:noop',
|
|
583
|
+
cache: true,
|
|
584
|
+
dependsOn: validationTargets,
|
|
585
|
+
metadata: {
|
|
586
|
+
technologies: ['nx'],
|
|
587
|
+
description: 'Run all architecture validations (cycles, unchanged, skip-level deps)',
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Create per-project ci target - Gradle-style composite target
|
|
594
|
+
* Runs lint, build, and test in parallel
|
|
595
|
+
* (with test depending on build via targetDefaults)
|
|
596
|
+
*
|
|
597
|
+
* NOTE: Type checking is done by the build target (@nx/js:tsc) during compilation.
|
|
598
|
+
*/
|
|
599
|
+
function createCiTarget(): TargetConfiguration {
|
|
600
|
+
return {
|
|
601
|
+
executor: 'nx:noop',
|
|
602
|
+
cache: true,
|
|
603
|
+
dependsOn: ['lint', 'build', 'test'],
|
|
604
|
+
metadata: {
|
|
605
|
+
technologies: ['nx'],
|
|
606
|
+
description: 'Run all CI checks: lint, build, and test (Gradle-style composite target)',
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function createHelpTarget(): TargetConfiguration {
|
|
612
|
+
return {
|
|
613
|
+
executor: '@webpieces/nx-webpieces-rules:help',
|
|
614
|
+
cache: false, // Never cache - always show help output
|
|
615
|
+
metadata: {
|
|
616
|
+
technologies: ['nx'],
|
|
617
|
+
description: 'Display help for @webpieces/nx-webpieces-rules commands and targets',
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Create per-project circular dependency checking target
|
|
624
|
+
* Runs on project root (.) to check ALL TypeScript files in the project
|
|
625
|
+
*/
|
|
626
|
+
function createCircularDepsTarget(projectRoot: string, targetName: string): TargetConfiguration {
|
|
627
|
+
return {
|
|
628
|
+
executor: 'nx:run-commands',
|
|
629
|
+
cache: true,
|
|
630
|
+
inputs: ['default'],
|
|
631
|
+
outputs: [] as string[],
|
|
632
|
+
options: {
|
|
633
|
+
command: 'npx madge --circular --extensions ts,tsx .',
|
|
634
|
+
cwd: projectRoot,
|
|
635
|
+
},
|
|
636
|
+
metadata: {
|
|
637
|
+
technologies: ['madge'],
|
|
638
|
+
description: 'Check for circular dependencies using madge',
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Check if a project should be excluded based on patterns
|
|
645
|
+
*/
|
|
646
|
+
function isExcluded(projectRoot: string, excludePatterns: string[]): boolean {
|
|
647
|
+
if (excludePatterns.length === 0) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Simple glob matching (could be enhanced with minimatch if needed)
|
|
652
|
+
return excludePatterns.some((pattern) => {
|
|
653
|
+
// Convert glob pattern to regex
|
|
654
|
+
const regexPattern = pattern
|
|
655
|
+
.replace(/\*\*/g, '.*') // ** matches any path
|
|
656
|
+
.replace(/\*/g, '[^/]*'); // * matches any string except /
|
|
657
|
+
|
|
658
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
659
|
+
return regex.test(projectRoot);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Export plugin as default for Nx
|
|
665
|
+
*/
|
|
666
|
+
export default { createNodesV2 };
|
package/src/toError.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight duplicate of @webpieces/core-util toError for use in dev-config.
|
|
3
|
+
* dev-config is Level 0 and cannot depend on core-util (also Level 0).
|
|
4
|
+
*/
|
|
5
|
+
// webpieces-disable no-any-unknown -- toError intentionally accepts unknown to safely convert any thrown value to Error
|
|
6
|
+
export function toError(err: unknown): Error {
|
|
7
|
+
if (err instanceof Error) {
|
|
8
|
+
return err;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (err && typeof err === 'object') {
|
|
12
|
+
if ('message' in err) {
|
|
13
|
+
const error = new Error(String(err.message));
|
|
14
|
+
|
|
15
|
+
if ('stack' in err && typeof err.stack === 'string') {
|
|
16
|
+
error.stack = err.stack;
|
|
17
|
+
}
|
|
18
|
+
if ('name' in err && typeof err.name === 'string') {
|
|
19
|
+
error.name = err.name;
|
|
20
|
+
}
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- must handle circular references without recursion
|
|
25
|
+
try {
|
|
26
|
+
return new Error(`Non-Error object thrown: ${JSON.stringify(err)}`);
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
//const error = toError(err);
|
|
29
|
+
void err;
|
|
30
|
+
return new Error('Non-Error object thrown (unable to stringify)');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const message = err == null ? 'Null or undefined thrown' : String(err);
|
|
35
|
+
return new Error(message);
|
|
36
|
+
}
|