@webpieces/nx-webpieces-rules 0.3.253 → 0.3.255
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.255",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/ai-hook-rules": "0.3.
|
|
25
|
-
"@webpieces/code-rules": "0.3.
|
|
26
|
-
"@webpieces/eslint-rules": "0.3.
|
|
27
|
-
"@webpieces/pr-gate": "0.3.
|
|
28
|
-
"@webpieces/rules-config": "0.3.
|
|
24
|
+
"@webpieces/ai-hook-rules": "0.3.255",
|
|
25
|
+
"@webpieces/code-rules": "0.3.255",
|
|
26
|
+
"@webpieces/eslint-rules": "0.3.255",
|
|
27
|
+
"@webpieces/pr-gate": "0.3.255",
|
|
28
|
+
"@webpieces/rules-config": "0.3.255",
|
|
29
29
|
"madge": "8.0.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
@@ -63,9 +63,11 @@ export declare function validateLibraryTypesMatch(graph: EnhancedGraph, problems
|
|
|
63
63
|
* `role-dependency` rule.
|
|
64
64
|
*
|
|
65
65
|
* A project's `role` is its function (server | designed-lib | lib | client).
|
|
66
|
-
* Apps
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* Apps are terminal — libraries and clients consume them, never the reverse:
|
|
67
|
+
* - a `client` is fully terminal: NOTHING may depend on it.
|
|
68
|
+
* - a `server` may only be depended upon by another `server` — the one
|
|
69
|
+
* legitimate case is a server-side orchestrator/e2e harness that boots
|
|
70
|
+
* other servers. A `lib`/`designed-lib`/`client` depending on a `server`
|
|
71
|
+
* inverts the dependency direction and is a violation.
|
|
70
72
|
*/
|
|
71
73
|
export declare function validateRoleDependencies(graph: EnhancedGraph, problems: string[]): void;
|
|
@@ -140,22 +140,31 @@ function validateLibraryTypesMatch(graph, problems) {
|
|
|
140
140
|
* `role-dependency` rule.
|
|
141
141
|
*
|
|
142
142
|
* A project's `role` is its function (server | designed-lib | lib | client).
|
|
143
|
-
* Apps
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
143
|
+
* Apps are terminal — libraries and clients consume them, never the reverse:
|
|
144
|
+
* - a `client` is fully terminal: NOTHING may depend on it.
|
|
145
|
+
* - a `server` may only be depended upon by another `server` — the one
|
|
146
|
+
* legitimate case is a server-side orchestrator/e2e harness that boots
|
|
147
|
+
* other servers. A `lib`/`designed-lib`/`client` depending on a `server`
|
|
148
|
+
* inverts the dependency direction and is a violation.
|
|
147
149
|
*/
|
|
148
150
|
function validateRoleDependencies(graph, problems) {
|
|
149
151
|
for (const [projectName, entry] of Object.entries(graph)) {
|
|
152
|
+
const fromRole = entry.role;
|
|
150
153
|
for (const dep of entry.dependsOn) {
|
|
151
154
|
const toRole = graph[dep]?.role;
|
|
152
155
|
if (toRole === undefined)
|
|
153
156
|
continue; // role resolution already flagged this project
|
|
154
157
|
if (!exports.APP_ROLES.includes(toRole))
|
|
155
158
|
continue;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
// A server may orchestrate/boot other servers (e.g. an e2e harness).
|
|
160
|
+
if (toRole === 'server' && fromRole === 'server')
|
|
161
|
+
continue;
|
|
162
|
+
const why = toRole === 'client'
|
|
163
|
+
? `a 'client' app is terminal and may never be depended upon`
|
|
164
|
+
: `a 'server' may only be depended upon by another 'server' (an orchestrator/e2e harness)`;
|
|
165
|
+
problems.push(`role-dependency: '${projectName}' (role:${fromRole ?? 'none'}) must not depend on ` +
|
|
166
|
+
`'${dep}' (role:${toRole}) — ${why}. Retag '${dep}' role:lib/role:designed-lib if it ` +
|
|
167
|
+
`is actually a library, or remove the dependency.`);
|
|
159
168
|
}
|
|
160
169
|
}
|
|
161
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-metadata.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Metadata Enrichment\n *\n * Fills the AI-oriented fields on each architecture/dependencies.json entry:\n * framework — from `framework:<x>` nx tag or package.json inference\n * shortDescription — first paragraph of the project's responsibilities.md\n * responsibilitiesFile — repo-relative path to the required responsibilities.md\n * designFile — repo-relative path to the generated DI design.json\n *\n * Validation is aggregated: ALL problems across ALL projects are collected and\n * thrown as one MetadataValidationError so a repo adopting this sees the full\n * seeding list in a single run. Callers must enrich BEFORE writing any file so\n * a failed run never clobbers dependencies.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { ProjectInfo } from './project-info';\nimport { resolveFramework } from './framework-resolver';\nimport { resolveRole } from './role-resolver';\nimport { extractShortDescription, validateShortDescription } from './responsibilities';\n\nexport const RESPONSIBILITIES_FILE_NAME = 'responsibilities.md';\n\n/**\n * Thrown when one or more projects fail metadata validation (missing/invalid\n * responsibilities.md, bad framework tags, ...). Executors catch this to point\n * AI at the webpieces.responsibilities.md instructions template.\n */\nexport class MetadataValidationError extends Error {\n constructor(public readonly problems: string[]) {\n super(\n `Architecture metadata validation failed (${problems.length} problem(s)):\\n` +\n problems.map((problem: string) => ` - ${problem}`).join('\\n')\n );\n this.name = 'MetadataValidationError';\n }\n}\n\n/**\n * Read per-project root + tags from nx's project graph.\n */\nexport async function collectProjectInfo(): Promise<Map<string, ProjectInfo>> {\n const projectGraph = await createProjectGraphAsync();\n const infos = new Map<string, ProjectInfo>();\n for (const [name, node] of Object.entries(projectGraph.nodes)) {\n infos.set(name, new ProjectInfo(name, node.data.root, node.data.tags ?? []));\n }\n return infos;\n}\n\n/**\n * Enrich every graph entry in place with framework, shortDescription,\n * responsibilitiesFile and designFile. Throws MetadataValidationError listing\n * every problem when any project fails validation.\n */\nexport function enrichGraph(\n graph: EnhancedGraph,\n infos: Map<string, ProjectInfo>,\n workspaceRoot: string\n): void {\n const problems: string[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const info = infos.get(projectName);\n if (!info) {\n problems.push(`${projectName}: not found in nx project graph`);\n continue;\n }\n\n const resolution = resolveFramework(info, workspaceRoot);\n if (resolution.problem !== null) {\n problems.push(resolution.problem);\n } else if (resolution.framework !== null) {\n entry.framework = resolution.framework;\n }\n\n const roleResolution = resolveRole(info);\n if (roleResolution.problem !== null) {\n problems.push(roleResolution.problem);\n } else if (roleResolution.role !== null) {\n entry.role = roleResolution.role;\n }\n\n enrichResponsibilities(entry, info, workspaceRoot, problems);\n\n // Only project.json projects get a generated design.json (see di-graph-targets.ts)\n if (fs.existsSync(path.join(workspaceRoot, info.root, 'project.json'))) {\n entry.designFile = toRepoRelative(info.root, 'design.json');\n }\n }\n\n validateLibraryTypesMatch(graph, problems);\n validateRoleDependencies(graph, problems);\n\n if (problems.length > 0) {\n throw new MetadataValidationError(problems);\n }\n}\n\n/**\n * Roles that are terminal APPS — nothing may depend on them. A server or a\n * client is a top-level runnable; being depended upon means it is really a\n * library and should be retagged `role:lib`/`role:designed-lib`.\n */\nexport const APP_ROLES: ReadonlyArray<string> = ['server', 'client'];\n\n/**\n * libType usable by any side — a dependency of this type is always allowed.\n */\nexport const ALL_LIB_TYPE = 'all';\n\n/**\n * `library-types-match-client` rule.\n *\n * A project's `framework` field is its libType — which client side it targets\n * (angular | react | express | all). This keeps side-specific code from\n * crossing sides: an `express` project must not pull in an `angular`-only\n * library (and vice-versa), and an `all` library — one that claims to be usable\n * by everyone — must not depend on a side-specific library (which would quietly\n * make it un-usable by the other sides).\n *\n * For every dependency edge A → B: allowed iff B is `all` or B has the same\n * libType as A. Every violation is appended to `problems` so `arch:generate`\n * fails with the full list.\n */\nexport function validateLibraryTypesMatch(graph: EnhancedGraph, problems: string[]): void {\n for (const [projectName, entry] of Object.entries(graph)) {\n const fromType = entry.framework;\n if (fromType === undefined) continue; // framework resolution already flagged this project\n\n for (const dep of entry.dependsOn) {\n const depEntry = graph[dep];\n const toType = depEntry?.framework;\n if (toType === undefined) continue;\n\n if (toType === ALL_LIB_TYPE || toType === fromType) continue;\n\n problems.push(\n `library-types-match-client: '${projectName}' (${fromType}) must not depend on ` +\n `'${dep}' (${toType}) — a '${fromType}' project may depend only on '${fromType}' ` +\n `or '${ALL_LIB_TYPE}' libraries. Fix the tag on one of them (framework:<angular|react|express|all>) ` +\n `or remove the dependency.`\n );\n }\n }\n}\n\n/**\n * `role-dependency` rule.\n *\n * A project's `role` is its function (server | designed-lib | lib | client).\n * Apps (`server`, `client`) are terminal — they consume libraries but are never\n * consumed. For every edge A → B, depending on a `server`/`client` is a\n * violation: B is really a library and must be retagged `role:lib` (or\n * `role:designed-lib`), or the dependency removed.\n */\nexport function validateRoleDependencies(graph: EnhancedGraph, problems: string[]): void {\n for (const [projectName, entry] of Object.entries(graph)) {\n for (const dep of entry.dependsOn) {\n const toRole = graph[dep]?.role;\n if (toRole === undefined) continue; // role resolution already flagged this project\n if (!APP_ROLES.includes(toRole)) continue;\n\n problems.push(\n `role-dependency: '${projectName}' must not depend on '${dep}' (role:${toRole}) — ` +\n `a '${toRole}' is a terminal app and may not be depended upon. Retag '${dep}' ` +\n `role:lib (or role:designed-lib) if it is actually a library, or remove the dependency.`\n );\n }\n }\n}\n\nfunction enrichResponsibilities(\n entry: EnhancedGraph[string],\n info: ProjectInfo,\n workspaceRoot: string,\n problems: string[]\n): void {\n const responsibilitiesFile = toRepoRelative(info.root, RESPONSIBILITIES_FILE_NAME);\n entry.responsibilitiesFile = responsibilitiesFile;\n\n const absolutePath = path.join(workspaceRoot, info.root, RESPONSIBILITIES_FILE_NAME);\n if (!fs.existsSync(absolutePath)) {\n problems.push(\n `${info.name}: missing required ${responsibilitiesFile} — create it with a heading, ` +\n `one short summary paragraph, then the full responsibilities of the module`\n );\n return;\n }\n\n const summary = extractShortDescription(fs.readFileSync(absolutePath, 'utf-8'));\n const summaryProblem = validateShortDescription(summary, responsibilitiesFile);\n if (summaryProblem !== null) {\n problems.push(`${info.name}: ${summaryProblem}`);\n return;\n }\n entry.shortDescription = summary;\n}\n\n/**\n * Repo-relative path with forward slashes (stable across platforms in the\n * committed JSON).\n */\nfunction toRepoRelative(projectRoot: string, fileName: string): string {\n return [projectRoot.replace(/\\\\/g, '/').replace(/\\/+$/, ''), fileName].join('/');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-metadata.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Metadata Enrichment\n *\n * Fills the AI-oriented fields on each architecture/dependencies.json entry:\n * framework — from `framework:<x>` nx tag or package.json inference\n * shortDescription — first paragraph of the project's responsibilities.md\n * responsibilitiesFile — repo-relative path to the required responsibilities.md\n * designFile — repo-relative path to the generated DI design.json\n *\n * Validation is aggregated: ALL problems across ALL projects are collected and\n * thrown as one MetadataValidationError so a repo adopting this sees the full\n * seeding list in a single run. Callers must enrich BEFORE writing any file so\n * a failed run never clobbers dependencies.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { ProjectInfo } from './project-info';\nimport { resolveFramework } from './framework-resolver';\nimport { resolveRole } from './role-resolver';\nimport { extractShortDescription, validateShortDescription } from './responsibilities';\n\nexport const RESPONSIBILITIES_FILE_NAME = 'responsibilities.md';\n\n/**\n * Thrown when one or more projects fail metadata validation (missing/invalid\n * responsibilities.md, bad framework tags, ...). Executors catch this to point\n * AI at the webpieces.responsibilities.md instructions template.\n */\nexport class MetadataValidationError extends Error {\n constructor(public readonly problems: string[]) {\n super(\n `Architecture metadata validation failed (${problems.length} problem(s)):\\n` +\n problems.map((problem: string) => ` - ${problem}`).join('\\n')\n );\n this.name = 'MetadataValidationError';\n }\n}\n\n/**\n * Read per-project root + tags from nx's project graph.\n */\nexport async function collectProjectInfo(): Promise<Map<string, ProjectInfo>> {\n const projectGraph = await createProjectGraphAsync();\n const infos = new Map<string, ProjectInfo>();\n for (const [name, node] of Object.entries(projectGraph.nodes)) {\n infos.set(name, new ProjectInfo(name, node.data.root, node.data.tags ?? []));\n }\n return infos;\n}\n\n/**\n * Enrich every graph entry in place with framework, shortDescription,\n * responsibilitiesFile and designFile. Throws MetadataValidationError listing\n * every problem when any project fails validation.\n */\nexport function enrichGraph(\n graph: EnhancedGraph,\n infos: Map<string, ProjectInfo>,\n workspaceRoot: string\n): void {\n const problems: string[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const info = infos.get(projectName);\n if (!info) {\n problems.push(`${projectName}: not found in nx project graph`);\n continue;\n }\n\n const resolution = resolveFramework(info, workspaceRoot);\n if (resolution.problem !== null) {\n problems.push(resolution.problem);\n } else if (resolution.framework !== null) {\n entry.framework = resolution.framework;\n }\n\n const roleResolution = resolveRole(info);\n if (roleResolution.problem !== null) {\n problems.push(roleResolution.problem);\n } else if (roleResolution.role !== null) {\n entry.role = roleResolution.role;\n }\n\n enrichResponsibilities(entry, info, workspaceRoot, problems);\n\n // Only project.json projects get a generated design.json (see di-graph-targets.ts)\n if (fs.existsSync(path.join(workspaceRoot, info.root, 'project.json'))) {\n entry.designFile = toRepoRelative(info.root, 'design.json');\n }\n }\n\n validateLibraryTypesMatch(graph, problems);\n validateRoleDependencies(graph, problems);\n\n if (problems.length > 0) {\n throw new MetadataValidationError(problems);\n }\n}\n\n/**\n * Roles that are terminal APPS — nothing may depend on them. A server or a\n * client is a top-level runnable; being depended upon means it is really a\n * library and should be retagged `role:lib`/`role:designed-lib`.\n */\nexport const APP_ROLES: ReadonlyArray<string> = ['server', 'client'];\n\n/**\n * libType usable by any side — a dependency of this type is always allowed.\n */\nexport const ALL_LIB_TYPE = 'all';\n\n/**\n * `library-types-match-client` rule.\n *\n * A project's `framework` field is its libType — which client side it targets\n * (angular | react | express | all). This keeps side-specific code from\n * crossing sides: an `express` project must not pull in an `angular`-only\n * library (and vice-versa), and an `all` library — one that claims to be usable\n * by everyone — must not depend on a side-specific library (which would quietly\n * make it un-usable by the other sides).\n *\n * For every dependency edge A → B: allowed iff B is `all` or B has the same\n * libType as A. Every violation is appended to `problems` so `arch:generate`\n * fails with the full list.\n */\nexport function validateLibraryTypesMatch(graph: EnhancedGraph, problems: string[]): void {\n for (const [projectName, entry] of Object.entries(graph)) {\n const fromType = entry.framework;\n if (fromType === undefined) continue; // framework resolution already flagged this project\n\n for (const dep of entry.dependsOn) {\n const depEntry = graph[dep];\n const toType = depEntry?.framework;\n if (toType === undefined) continue;\n\n if (toType === ALL_LIB_TYPE || toType === fromType) continue;\n\n problems.push(\n `library-types-match-client: '${projectName}' (${fromType}) must not depend on ` +\n `'${dep}' (${toType}) — a '${fromType}' project may depend only on '${fromType}' ` +\n `or '${ALL_LIB_TYPE}' libraries. Fix the tag on one of them (framework:<angular|react|express|all>) ` +\n `or remove the dependency.`\n );\n }\n }\n}\n\n/**\n * `role-dependency` rule.\n *\n * A project's `role` is its function (server | designed-lib | lib | client).\n * Apps are terminal — libraries and clients consume them, never the reverse:\n * - a `client` is fully terminal: NOTHING may depend on it.\n * - a `server` may only be depended upon by another `server` — the one\n * legitimate case is a server-side orchestrator/e2e harness that boots\n * other servers. A `lib`/`designed-lib`/`client` depending on a `server`\n * inverts the dependency direction and is a violation.\n */\nexport function validateRoleDependencies(graph: EnhancedGraph, problems: string[]): void {\n for (const [projectName, entry] of Object.entries(graph)) {\n const fromRole = entry.role;\n for (const dep of entry.dependsOn) {\n const toRole = graph[dep]?.role;\n if (toRole === undefined) continue; // role resolution already flagged this project\n if (!APP_ROLES.includes(toRole)) continue;\n // A server may orchestrate/boot other servers (e.g. an e2e harness).\n if (toRole === 'server' && fromRole === 'server') continue;\n\n const why =\n toRole === 'client'\n ? `a 'client' app is terminal and may never be depended upon`\n : `a 'server' may only be depended upon by another 'server' (an orchestrator/e2e harness)`;\n problems.push(\n `role-dependency: '${projectName}' (role:${fromRole ?? 'none'}) must not depend on ` +\n `'${dep}' (role:${toRole}) — ${why}. Retag '${dep}' role:lib/role:designed-lib if it ` +\n `is actually a library, or remove the dependency.`\n );\n }\n }\n}\n\nfunction enrichResponsibilities(\n entry: EnhancedGraph[string],\n info: ProjectInfo,\n workspaceRoot: string,\n problems: string[]\n): void {\n const responsibilitiesFile = toRepoRelative(info.root, RESPONSIBILITIES_FILE_NAME);\n entry.responsibilitiesFile = responsibilitiesFile;\n\n const absolutePath = path.join(workspaceRoot, info.root, RESPONSIBILITIES_FILE_NAME);\n if (!fs.existsSync(absolutePath)) {\n problems.push(\n `${info.name}: missing required ${responsibilitiesFile} — create it with a heading, ` +\n `one short summary paragraph, then the full responsibilities of the module`\n );\n return;\n }\n\n const summary = extractShortDescription(fs.readFileSync(absolutePath, 'utf-8'));\n const summaryProblem = validateShortDescription(summary, responsibilitiesFile);\n if (summaryProblem !== null) {\n problems.push(`${info.name}: ${summaryProblem}`);\n return;\n }\n entry.shortDescription = summary;\n}\n\n/**\n * Repo-relative path with forward slashes (stable across platforms in the\n * committed JSON).\n */\nfunction toRepoRelative(projectRoot: string, fileName: string): string {\n return [projectRoot.replace(/\\\\/g, '/').replace(/\\/+$/, ''), fileName].join('/');\n}\n"]}
|