@webpieces/rules-config 0.3.232 → 0.3.233

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/rules-config",
3
- "version": "0.3.232",
3
+ "version": "0.3.233",
4
4
  "description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -34,6 +34,11 @@ export declare function resolveBase(workspaceRoot: string): DiffRange;
34
34
  * Changed files between base and head (or base→working-tree when head is omitted). When head is
35
35
  * omitted, untracked files are unioned in too (matching `nx affected`). `tsOnly` (default true)
36
36
  * restricts to *.ts/*.tsx and drops test files; pass false for an all-files diff.
37
+ *
38
+ * Deletions are excluded (`--diff-filter=d`): every consumer reasons about a file's current
39
+ * content or location, and a path that no longer exists can't violate anything. This also fixes
40
+ * renames — without rename detection git reports a rename as delete+add, so filtering the delete
41
+ * side leaves only the file's NEW path in the list.
37
42
  */
38
43
  export declare function getChangedFiles(workspaceRoot: string, base: string, head?: string, opts?: ChangedFilesOptions): string[];
39
44
  /**
package/src/diff-scope.js CHANGED
@@ -83,6 +83,11 @@ function resolveBase(workspaceRoot) {
83
83
  * Changed files between base and head (or base→working-tree when head is omitted). When head is
84
84
  * omitted, untracked files are unioned in too (matching `nx affected`). `tsOnly` (default true)
85
85
  * restricts to *.ts/*.tsx and drops test files; pass false for an all-files diff.
86
+ *
87
+ * Deletions are excluded (`--diff-filter=d`): every consumer reasons about a file's current
88
+ * content or location, and a path that no longer exists can't violate anything. This also fixes
89
+ * renames — without rename detection git reports a rename as delete+add, so filtering the delete
90
+ * side leaves only the file's NEW path in the list.
86
91
  */
87
92
  // webpieces-disable max-lines-new-methods -- git command handling with untracked files needs several code paths
88
93
  function getChangedFiles(workspaceRoot, base, head, opts) {
@@ -92,7 +97,7 @@ function getChangedFiles(workspaceRoot, base, head, opts) {
92
97
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
93
98
  try {
94
99
  const diffTarget = head ? `${base} ${head}` : base;
95
- const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget}${glob}`, {
100
+ const output = (0, child_process_1.execSync)(`git diff --name-only --diff-filter=d ${diffTarget}${glob}`, {
96
101
  cwd: workspaceRoot,
97
102
  encoding: 'utf-8',
98
103
  });
@@ -1 +1 @@
1
- {"version":3,"file":"diff-scope.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/diff-scope.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared git-diff + diff-scoping helpers for ALL rule validators (code-rules) and nx executors\n * (nx-webpieces-rules). Centralized here in rules-config because it is the one package both of\n * those depend on, and it already shells out to git (see skip-rule.ts).\n *\n * Before this module these functions were copy-pasted ~15× (one private copy per validator + the\n * validate-ts-in-src executor). They are now defined once; every consumer imports from\n * `@webpieces/rules-config`. The two historical variants are preserved as parameters:\n * - `getChangedFiles(..., { tsOnly: false })` reproduces validate-dtos' all-files diff.\n * - `detectBase` replaces both the nested-try and the ref-loop spellings (identical behavior).\n */\n\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n/** A git diff range: the base ref to compare against and an optional head (else the working tree). */\nexport class DiffRange {\n base?: string;\n head?: string;\n}\n\n/** Options for getChangedFiles. `tsOnly` (default true) restricts to *.ts/*.tsx and drops test files. */\nexport class ChangedFilesOptions {\n tsOnly?: boolean;\n}\n\n/**\n * A file is \"a test file\" (excluded from diff-scoped rules) when it is a .spec/.test file or lives\n * under a __tests__/ directory. Union of every per-validator filter so no consumer loses coverage.\n */\nfunction isTestFile(file: string): boolean {\n return file.includes('.spec.ts') || file.includes('.test.ts') || file.includes('__tests__/');\n}\n\n/**\n * Auto-detect the diff base: the merge-base of HEAD with origin/main, falling back to local main.\n * Returns null when neither ref resolves (e.g. shallow clone with no main) — callers treat null as\n * \"no base detected\" and typically skip.\n */\nexport function detectBase(workspaceRoot: string): string | null {\n for (const ref of ['origin/main', 'main']) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const merged = execSync(`git merge-base HEAD ${ref}`, {\n cwd: workspaceRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (merged) return merged;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — try the next ref\n }\n }\n return null;\n}\n\n/**\n * Resolve the diff range a rule should compare against. Honors nx's NX_BASE / NX_HEAD (set by\n * `nx affected --base=.. --head=..`); when NX_BASE is unset, auto-detects via detectBase. A returned\n * `base` of undefined means \"could not determine a base\" (caller should skip).\n */\nexport function resolveBase(workspaceRoot: string): DiffRange {\n const range = new DiffRange();\n range.base = process.env['NX_BASE'];\n range.head = process.env['NX_HEAD'];\n if (!range.base) {\n range.base = detectBase(workspaceRoot) ?? undefined;\n }\n return range;\n}\n\n/**\n * Changed files between base and head (or base→working-tree when head is omitted). When head is\n * omitted, untracked files are unioned in too (matching `nx affected`). `tsOnly` (default true)\n * restricts to *.ts/*.tsx and drops test files; pass false for an all-files diff.\n */\n// webpieces-disable max-lines-new-methods -- git command handling with untracked files needs several code paths\nexport function getChangedFiles(\n workspaceRoot: string,\n base: string,\n head?: string,\n opts?: ChangedFilesOptions,\n): string[] {\n const tsOnly = opts?.tsOnly ?? true;\n const glob = tsOnly ? \" -- '*.ts' '*.tsx'\" : '';\n const keep = (f: string): boolean => f.length > 0 && (!tsOnly || !isTestFile(f));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget}${glob}`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output.trim().split('\\n').filter(keep);\n\n // Working-tree comparison (no head): also include untracked files, as nx affected does.\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard${glob}`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput.trim().split('\\n').filter(keep);\n return Array.from(new Set([...changedFiles, ...untrackedFiles]));\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — ls-files failure falls back to the tracked list\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — git diff failure returns an empty list\n return [];\n }\n}\n\n/**\n * Diff content for a single file. When the file is untracked (and no head is given) a synthetic\n * all-added diff is produced so new files count as fully-changed.\n */\nexport function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const diff = execSync(`git diff ${diffTarget} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n\n if (!diff && !head) {\n const fullPath = path.join(workspaceRoot, file);\n if (fs.existsSync(fullPath)) {\n const isUntracked = execSync(`git ls-files --others --exclude-standard \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }).trim();\n\n if (isUntracked) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return content.split('\\n').map((l: string) => `+${l}`).join('\\n');\n }\n }\n }\n\n return diff;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — git diff failure returns no diff\n return '';\n }\n}\n\n/**\n * Parse a unified diff and return the set of added/changed line numbers (the `+` lines per hunk).\n * This is the basis of NEW_AND_MODIFIED_CODE (line-level) scoping.\n */\nexport function getChangedLineNumbers(diffContent: string): Set<number> {\n const changedLines = new Set<number>();\n const lines = diffContent.split('\\n');\n let currentLine = 0;\n\n for (const line of lines) {\n const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (hunkMatch) {\n currentLine = parseInt(hunkMatch[1], 10);\n continue;\n }\n\n if (line.startsWith('+') && !line.startsWith('+++')) {\n changedLines.add(currentLine);\n currentLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n // Deletions don't advance the new-file line counter.\n } else {\n currentLine++;\n }\n }\n\n return changedLines;\n}\n\n/**\n * Method names whose signature line is a `+` addition in the diff — the basis of \"NEW\" methods.\n */\nexport function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n const patterns = [\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n /^\\+\\s*(?:(?:public|private|protected)\\s+)?(?:static\\s+)?(?:async\\s+)?(\\w+)\\s*\\(/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n const methodName = match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * True if any line in [startLine, endLine] is in the changedLines set.\n */\nexport function hasChangesInRange(startLine: number, endLine: number, changedLines: Set<number>): boolean {\n for (let line = startLine; line <= endLine; line++) {\n if (changedLines.has(line)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * True if a node (method/function) is newly added or has any changed line in its range.\n */\nexport function isNewOrModified(\n name: string,\n startLine: number,\n endLine: number,\n changedLines: Set<number>,\n newMethodNames: Set<string>,\n): boolean {\n if (newMethodNames.has(name)) return true;\n return hasChangesInRange(startLine, endLine, changedLines);\n}\n"]}
1
+ {"version":3,"file":"diff-scope.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/diff-scope.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared git-diff + diff-scoping helpers for ALL rule validators (code-rules) and nx executors\n * (nx-webpieces-rules). Centralized here in rules-config because it is the one package both of\n * those depend on, and it already shells out to git (see skip-rule.ts).\n *\n * Before this module these functions were copy-pasted ~15× (one private copy per validator + the\n * validate-ts-in-src executor). They are now defined once; every consumer imports from\n * `@webpieces/rules-config`. The two historical variants are preserved as parameters:\n * - `getChangedFiles(..., { tsOnly: false })` reproduces validate-dtos' all-files diff.\n * - `detectBase` replaces both the nested-try and the ref-loop spellings (identical behavior).\n */\n\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n/** A git diff range: the base ref to compare against and an optional head (else the working tree). */\nexport class DiffRange {\n base?: string;\n head?: string;\n}\n\n/** Options for getChangedFiles. `tsOnly` (default true) restricts to *.ts/*.tsx and drops test files. */\nexport class ChangedFilesOptions {\n tsOnly?: boolean;\n}\n\n/**\n * A file is \"a test file\" (excluded from diff-scoped rules) when it is a .spec/.test file or lives\n * under a __tests__/ directory. Union of every per-validator filter so no consumer loses coverage.\n */\nfunction isTestFile(file: string): boolean {\n return file.includes('.spec.ts') || file.includes('.test.ts') || file.includes('__tests__/');\n}\n\n/**\n * Auto-detect the diff base: the merge-base of HEAD with origin/main, falling back to local main.\n * Returns null when neither ref resolves (e.g. shallow clone with no main) — callers treat null as\n * \"no base detected\" and typically skip.\n */\nexport function detectBase(workspaceRoot: string): string | null {\n for (const ref of ['origin/main', 'main']) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const merged = execSync(`git merge-base HEAD ${ref}`, {\n cwd: workspaceRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n if (merged) return merged;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — try the next ref\n }\n }\n return null;\n}\n\n/**\n * Resolve the diff range a rule should compare against. Honors nx's NX_BASE / NX_HEAD (set by\n * `nx affected --base=.. --head=..`); when NX_BASE is unset, auto-detects via detectBase. A returned\n * `base` of undefined means \"could not determine a base\" (caller should skip).\n */\nexport function resolveBase(workspaceRoot: string): DiffRange {\n const range = new DiffRange();\n range.base = process.env['NX_BASE'];\n range.head = process.env['NX_HEAD'];\n if (!range.base) {\n range.base = detectBase(workspaceRoot) ?? undefined;\n }\n return range;\n}\n\n/**\n * Changed files between base and head (or base→working-tree when head is omitted). When head is\n * omitted, untracked files are unioned in too (matching `nx affected`). `tsOnly` (default true)\n * restricts to *.ts/*.tsx and drops test files; pass false for an all-files diff.\n *\n * Deletions are excluded (`--diff-filter=d`): every consumer reasons about a file's current\n * content or location, and a path that no longer exists can't violate anything. This also fixes\n * renames — without rename detection git reports a rename as delete+add, so filtering the delete\n * side leaves only the file's NEW path in the list.\n */\n// webpieces-disable max-lines-new-methods -- git command handling with untracked files needs several code paths\nexport function getChangedFiles(\n workspaceRoot: string,\n base: string,\n head?: string,\n opts?: ChangedFilesOptions,\n): string[] {\n const tsOnly = opts?.tsOnly ?? true;\n const glob = tsOnly ? \" -- '*.ts' '*.tsx'\" : '';\n const keep = (f: string): boolean => f.length > 0 && (!tsOnly || !isTestFile(f));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only --diff-filter=d ${diffTarget}${glob}`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output.trim().split('\\n').filter(keep);\n\n // Working-tree comparison (no head): also include untracked files, as nx affected does.\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard${glob}`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput.trim().split('\\n').filter(keep);\n return Array.from(new Set([...changedFiles, ...untrackedFiles]));\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — ls-files failure falls back to the tracked list\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — git diff failure returns an empty list\n return [];\n }\n}\n\n/**\n * Diff content for a single file. When the file is untracked (and no head is given) a synthetic\n * all-added diff is produced so new files count as fully-changed.\n */\nexport function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const diff = execSync(`git diff ${diffTarget} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n\n if (!diff && !head) {\n const fullPath = path.join(workspaceRoot, file);\n if (fs.existsSync(fullPath)) {\n const isUntracked = execSync(`git ls-files --others --exclude-standard \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }).trim();\n\n if (isUntracked) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return content.split('\\n').map((l: string) => `+${l}`).join('\\n');\n }\n }\n }\n\n return diff;\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — git diff failure returns no diff\n return '';\n }\n}\n\n/**\n * Parse a unified diff and return the set of added/changed line numbers (the `+` lines per hunk).\n * This is the basis of NEW_AND_MODIFIED_CODE (line-level) scoping.\n */\nexport function getChangedLineNumbers(diffContent: string): Set<number> {\n const changedLines = new Set<number>();\n const lines = diffContent.split('\\n');\n let currentLine = 0;\n\n for (const line of lines) {\n const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (hunkMatch) {\n currentLine = parseInt(hunkMatch[1], 10);\n continue;\n }\n\n if (line.startsWith('+') && !line.startsWith('+++')) {\n changedLines.add(currentLine);\n currentLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n // Deletions don't advance the new-file line counter.\n } else {\n currentLine++;\n }\n }\n\n return changedLines;\n}\n\n/**\n * Method names whose signature line is a `+` addition in the diff — the basis of \"NEW\" methods.\n */\nexport function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n const patterns = [\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n /^\\+\\s*(?:(?:public|private|protected)\\s+)?(?:static\\s+)?(?:async\\s+)?(\\w+)\\s*\\(/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n const methodName = match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * True if any line in [startLine, endLine] is in the changedLines set.\n */\nexport function hasChangesInRange(startLine: number, endLine: number, changedLines: Set<number>): boolean {\n for (let line = startLine; line <= endLine; line++) {\n if (changedLines.has(line)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * True if a node (method/function) is newly added or has any changed line in its range.\n */\nexport function isNewOrModified(\n name: string,\n startLine: number,\n endLine: number,\n changedLines: Set<number>,\n newMethodNames: Set<string>,\n): boolean {\n if (newMethodNames.has(name)) return true;\n return hasChangesInRange(startLine, endLine, changedLines);\n}\n"]}