@webpieces/code-rules 0.3.247 → 0.3.249

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/code-rules",
3
- "version": "0.3.247",
3
+ "version": "0.3.249",
4
4
  "description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -20,7 +20,7 @@
20
20
  "directory": "packages/tooling/code-rules"
21
21
  },
22
22
  "dependencies": {
23
- "@webpieces/rules-config": "0.3.247"
23
+ "@webpieces/rules-config": "0.3.249"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
@@ -24,13 +24,13 @@
24
24
  * { "name": "my-lib", "tags": ["framework:all"] }
25
25
  *
26
26
  * ============================================================================
27
- * MODES (PROJECT-BASED)
27
+ * MODES (PROJECT-LEVEL)
28
28
  * ============================================================================
29
- * - OFF: Skip validation entirely
30
- * - NEW_AND_MODIFIED_CODE
31
- * / NEW_AND_MODIFIED_FILES: Require a framework tag on every project that owns
32
- * a changed .ts file (both modes behave the same here
33
- * the check is project-level, not line-level).
29
+ * - OFF: Skip validation entirely.
30
+ * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
31
+ * changed file (not just .ts, and not line-scoped) the
32
+ * check is inherently project-level. nx `affected` already
33
+ * narrows execution to the changed projects.
34
34
  */
35
35
  import { FrameworkTagConfig } from '@webpieces/rules-config';
36
36
  import { CodeValidator, ExecutorResult } from './code-validator';
@@ -25,13 +25,13 @@
25
25
  * { "name": "my-lib", "tags": ["framework:all"] }
26
26
  *
27
27
  * ============================================================================
28
- * MODES (PROJECT-BASED)
28
+ * MODES (PROJECT-LEVEL)
29
29
  * ============================================================================
30
- * - OFF: Skip validation entirely
31
- * - NEW_AND_MODIFIED_CODE
32
- * / NEW_AND_MODIFIED_FILES: Require a framework tag on every project that owns
33
- * a changed .ts file (both modes behave the same here
34
- * the check is project-level, not line-level).
30
+ * - OFF: Skip validation entirely.
31
+ * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
32
+ * changed file (not just .ts, and not line-scoped) the
33
+ * check is inherently project-level. nx `affected` already
34
+ * narrows execution to the changed projects.
35
35
  */
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.FrameworkTagValidator = void 0;
@@ -181,9 +181,11 @@ async function runValidatorImpl(options, workspaceRoot) {
181
181
  console.log(` Base: ${base}`);
182
182
  console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
183
183
  console.log('');
184
- const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
184
+ // Project-level rule: ANY changed file (not just .ts) makes its owning
185
+ // project require a tag, so include non-code files too.
186
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head, { tsOnly: false });
185
187
  if (changedFiles.length === 0) {
186
- console.log('✅ No TypeScript files changed');
188
+ console.log('✅ No changed files in any project');
187
189
  return { success: true };
188
190
  }
189
191
  const untagged = findUntaggedProjects(workspaceRoot, changedFiles);
@@ -1 +1 @@
1
- {"version":3,"file":"validate-framework-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-framework-tag.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Framework Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `framework:<value>` nx tag in its project.json. That tag is the project's\n * \"libType\" — which client side it targets:\n *\n * framework:angular | framework:react | framework:express | framework:all\n *\n * (\"all\" = a library usable by any side.) It is the source of truth for the\n * `framework` field written into architecture/dependencies.json and for the\n * `library-types-match-client` rule, which uses it to keep an express project\n * from depending on an angular-only library (and vice-versa).\n *\n * ============================================================================\n * VIOLATION (BAD)\n * ============================================================================\n * A project.json with no `framework:` tag whose source was modified:\n * { \"name\": \"my-lib\", \"tags\": [] }\n *\n * ============================================================================\n * ALLOWED\n * ============================================================================\n * { \"name\": \"my-lib\", \"tags\": [\"framework:all\"] }\n *\n * ============================================================================\n * MODES (PROJECT-BASED)\n * ============================================================================\n * - OFF: Skip validation entirely\n * - NEW_AND_MODIFIED_CODE\n * / NEW_AND_MODIFIED_FILES: Require a framework tag on every project that owns\n * a changed .ts file (both modes behave the same here\n * — the check is project-level, not line-level).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ModifiedCodeMode, FrameworkTagConfig, detectBase, getChangedFiles, toError } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];\n\n/**\n * A project (identified by its project.json) that owns at least one changed\n * file but is missing a `framework:` tag.\n */\nclass UntaggedProject {\n constructor(\n public readonly name: string,\n public readonly projectJsonPath: string\n ) {}\n}\n\n/**\n * Raw shape parsed out of a project.json (only the fields this rule reads).\n */\ntype RawProjectJson = { name?: string; tags?: string[] };\n\n/**\n * The parts of a project.json this rule reads.\n */\nclass ProjectJson {\n constructor(\n public readonly name: string | null,\n public readonly tags: string[]\n ) {}\n}\n\n/**\n * Parse the `name` + `tags` from a project.json. Returns an empty ProjectJson\n * (no name, no tags) when the file is unreadable/unparseable — config\n * validation owns malformed-json reporting; this rule just nudges for a tag and\n * must never crash the build.\n */\nfunction readProjectJson(projectJsonPath: string, workspaceRoot: string): ProjectJson {\n const fullPath = path.join(workspaceRoot, projectJsonPath);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed: RawProjectJson = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));\n const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;\n const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag: string) => typeof tag === 'string') : [];\n return new ProjectJson(name, tags);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — malformed project.json is not this rule's concern\n return new ProjectJson(null, []);\n }\n}\n\n/**\n * Walk up from a changed file to the nearest ancestor directory containing a\n * project.json (the file's owning nx project). Returns the repo-relative\n * project.json path, or null when the file belongs to no project (e.g. a\n * workspace-root config file).\n */\nfunction findOwningProjectJson(changedFile: string, workspaceRoot: string): string | null {\n let dir = path.dirname(changedFile);\n while (true) {\n const candidate = path.join(dir, 'project.json');\n if (fs.existsSync(path.join(workspaceRoot, candidate))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir || dir === '.' || dir === '') {\n return null;\n }\n dir = parent;\n }\n}\n\nfunction hasFrameworkTag(tags: string[]): boolean {\n return tags.some((tag: string) => tag.startsWith(FRAMEWORK_TAG_PREFIX) && tag.slice(FRAMEWORK_TAG_PREFIX.length).trim().length > 0);\n}\n\nexport function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): UntaggedProject[] {\n const projectJsonPaths = new Set<string>();\n for (const file of changedFiles) {\n const owning = findOwningProjectJson(file, workspaceRoot);\n if (owning !== null) {\n projectJsonPaths.add(owning);\n }\n }\n\n const untagged: UntaggedProject[] = [];\n for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {\n const projectJson = readProjectJson(projectJsonPath, workspaceRoot);\n if (!hasFrameworkTag(projectJson.tags)) {\n const name = projectJson.name ?? path.dirname(projectJsonPath);\n untagged.push(new UntaggedProject(name, projectJsonPath));\n }\n }\n return untagged;\n}\n\nfunction reportViolations(untagged: UntaggedProject[], knownTypes: string[], mode: ModifiedCodeMode): void {\n const suggestion = knownTypes.map((t: string) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare which client side it targets (a framework tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (\"all\" = a library usable by any side — angular, react and express can all import it.)');\n console.error(' This libType drives architecture/dependencies.json and the library-types-match-client rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${FRAMEWORK_TAG_PREFIX}all\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nfunction resolveMode(normalMode: ModifiedCodeMode, epoch: number | undefined, branchPattern: string | undefined): ModifiedCodeMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping framework-tag validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: FrameworkTagConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ModifiedCodeMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : DEFAULT_KNOWN_TYPES;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping framework-tag validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Framework Tag\\n');\n console.log(` Mode: ${mode}`);\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 framework-tag validation (could not detect base branch)');\n console.log('');\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 changedFiles = getChangedFiles(workspaceRoot, base, head);\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n const untagged = findUntaggedProjects(workspaceRoot, changedFiles);\n if (untagged.length === 0) {\n console.log('✅ Every modified project declares a framework tag');\n return { success: true };\n }\n\n reportViolations(untagged, knownTypes, mode);\n return { success: false };\n}\n\nexport class FrameworkTagValidator extends CodeValidator<FrameworkTagConfig> {\n constructor(config: FrameworkTagConfig) {\n super(config, 'framework-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
1
+ {"version":3,"file":"validate-framework-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-framework-tag.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Framework Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `framework:<value>` nx tag in its project.json. That tag is the project's\n * \"libType\" — which client side it targets:\n *\n * framework:angular | framework:react | framework:express | framework:all\n *\n * (\"all\" = a library usable by any side.) It is the source of truth for the\n * `framework` field written into architecture/dependencies.json and for the\n * `library-types-match-client` rule, which uses it to keep an express project\n * from depending on an angular-only library (and vice-versa).\n *\n * ============================================================================\n * VIOLATION (BAD)\n * ============================================================================\n * A project.json with no `framework:` tag whose source was modified:\n * { \"name\": \"my-lib\", \"tags\": [] }\n *\n * ============================================================================\n * ALLOWED\n * ============================================================================\n * { \"name\": \"my-lib\", \"tags\": [\"framework:all\"] }\n *\n * ============================================================================\n * MODES (PROJECT-LEVEL)\n * ============================================================================\n * - OFF: Skip validation entirely.\n * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY\n * changed file (not just .ts, and not line-scoped) — the\n * check is inherently project-level. nx `affected` already\n * narrows execution to the changed projects.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectMode, FrameworkTagConfig, detectBase, getChangedFiles, toError } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];\n\n/**\n * A project (identified by its project.json) that owns at least one changed\n * file but is missing a `framework:` tag.\n */\nclass UntaggedProject {\n constructor(\n public readonly name: string,\n public readonly projectJsonPath: string\n ) {}\n}\n\n/**\n * Raw shape parsed out of a project.json (only the fields this rule reads).\n */\ntype RawProjectJson = { name?: string; tags?: string[] };\n\n/**\n * The parts of a project.json this rule reads.\n */\nclass ProjectJson {\n constructor(\n public readonly name: string | null,\n public readonly tags: string[]\n ) {}\n}\n\n/**\n * Parse the `name` + `tags` from a project.json. Returns an empty ProjectJson\n * (no name, no tags) when the file is unreadable/unparseable — config\n * validation owns malformed-json reporting; this rule just nudges for a tag and\n * must never crash the build.\n */\nfunction readProjectJson(projectJsonPath: string, workspaceRoot: string): ProjectJson {\n const fullPath = path.join(workspaceRoot, projectJsonPath);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed: RawProjectJson = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));\n const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;\n const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag: string) => typeof tag === 'string') : [];\n return new ProjectJson(name, tags);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — malformed project.json is not this rule's concern\n return new ProjectJson(null, []);\n }\n}\n\n/**\n * Walk up from a changed file to the nearest ancestor directory containing a\n * project.json (the file's owning nx project). Returns the repo-relative\n * project.json path, or null when the file belongs to no project (e.g. a\n * workspace-root config file).\n */\nfunction findOwningProjectJson(changedFile: string, workspaceRoot: string): string | null {\n let dir = path.dirname(changedFile);\n while (true) {\n const candidate = path.join(dir, 'project.json');\n if (fs.existsSync(path.join(workspaceRoot, candidate))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir || dir === '.' || dir === '') {\n return null;\n }\n dir = parent;\n }\n}\n\nfunction hasFrameworkTag(tags: string[]): boolean {\n return tags.some((tag: string) => tag.startsWith(FRAMEWORK_TAG_PREFIX) && tag.slice(FRAMEWORK_TAG_PREFIX.length).trim().length > 0);\n}\n\nexport function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): UntaggedProject[] {\n const projectJsonPaths = new Set<string>();\n for (const file of changedFiles) {\n const owning = findOwningProjectJson(file, workspaceRoot);\n if (owning !== null) {\n projectJsonPaths.add(owning);\n }\n }\n\n const untagged: UntaggedProject[] = [];\n for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {\n const projectJson = readProjectJson(projectJsonPath, workspaceRoot);\n if (!hasFrameworkTag(projectJson.tags)) {\n const name = projectJson.name ?? path.dirname(projectJsonPath);\n untagged.push(new UntaggedProject(name, projectJsonPath));\n }\n }\n return untagged;\n}\n\nfunction reportViolations(untagged: UntaggedProject[], knownTypes: string[], mode: ProjectMode): void {\n const suggestion = knownTypes.map((t: string) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare which client side it targets (a framework tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (\"all\" = a library usable by any side — angular, react and express can all import it.)');\n console.error(' This libType drives architecture/dependencies.json and the library-types-match-client rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${FRAMEWORK_TAG_PREFIX}all\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nfunction resolveMode(normalMode: ProjectMode, epoch: number | undefined, branchPattern: string | undefined): ProjectMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping framework-tag validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: FrameworkTagConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ProjectMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : DEFAULT_KNOWN_TYPES;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping framework-tag validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Framework Tag\\n');\n console.log(` Mode: ${mode}`);\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 framework-tag validation (could not detect base branch)');\n console.log('');\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 // Project-level rule: ANY changed file (not just .ts) makes its owning\n // project require a tag, so include non-code files too.\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false });\n if (changedFiles.length === 0) {\n console.log('✅ No changed files in any project');\n return { success: true };\n }\n\n const untagged = findUntaggedProjects(workspaceRoot, changedFiles);\n if (untagged.length === 0) {\n console.log('✅ Every modified project declares a framework tag');\n return { success: true };\n }\n\n reportViolations(untagged, knownTypes, mode);\n return { success: false };\n}\n\nexport class FrameworkTagValidator extends CodeValidator<FrameworkTagConfig> {\n constructor(config: FrameworkTagConfig) {\n super(config, 'framework-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}