@webpieces/rules-config 0.3.232 → 0.3.234
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 +1 -1
- package/src/config-file.d.ts +1 -0
- package/src/config-file.js.map +1 -1
- package/src/diff-scope.d.ts +5 -0
- package/src/diff-scope.js +6 -1
- package/src/diff-scope.js.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +12 -3
- package/src/index.js.map +1 -1
- package/src/load-config.d.ts +4 -1
- package/src/load-config.js +18 -3
- package/src/load-config.js.map +1 -1
- package/src/match-rules-config.d.ts +45 -0
- package/src/match-rules-config.js +173 -0
- package/src/match-rules-config.js.map +1 -0
- package/src/validate-config.d.ts +8 -0
- package/src/validate-config.js +124 -25
- package/src/validate-config.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.234",
|
|
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",
|
package/src/config-file.d.ts
CHANGED
package/src/config-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;AAuCA,wCASC;AAKD,sCAaC;;AAlED,+CAAyB;AACzB,mDAA6B;AAE7B,uDAAkD;AAClD,yCAAqC;AAExB,QAAA,eAAe,GAAG,uBAAuB,CAAC;AA8BvD;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC3C,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,OAAO,IAAI,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAe,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,UAAkB;IAC5C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CACnB,sEAAsE;YACtE,gBAAgB,KAAK,CAAC,OAAO,IAAI;YACjC,SAAS,UAAU,EAAE,CACxB,CAAC;IACN,CAAC;AACL,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\nexport const CONFIG_FILENAME = 'webpieces.config.json';\n\n// Raw shape of webpieces.config.json as parsed from JSON, before validation/typing.\n// - `rules` — code-style validators (scope edit/file).\n// - `hookGuards` — git/PR/branch protection guards (scope bash).\n// - `commands` — gated command config the guards point at; `pr-gate` lives inside it. Carried as\n// opaque JSON because its nested `gates` array can't be expressed in the FieldDef\n// schema; validated structurally by validateCommandsSection.\n// - `pr-gate` — DEPRECATED top-level block (pre-migration layout). Read only as a back-compat\n// fallback / to emit a \"move it under commands\" migration error.\n// webpieces-disable no-any-unknown -- consumer JSON config has opaque rule option values\nexport interface RawConfigFile {\n extends?: string;\n rules?: Record<string, Record<string, unknown>>;\n hookGuards?: Record<string, Record<string, unknown>>;\n // webpieces-disable no-any-unknown -- opaque commands JSON, validated by validateCommandsSection\n commands?: unknown;\n // REQUIRED top-level block: two glob lists that suppress hook enforcement per file path.\n // Opaque here (validated structurally by validateExcludePaths, then parsed into ExcludePaths).\n // webpieces-disable no-any-unknown -- opaque excludePaths JSON, validated by validateExcludePaths\n excludePaths?: unknown;\n // REQUIRED top-level array of client-authored content guards (regex patterns + message + scoping).\n // Opaque here; validated structurally by validateMatchRulesSection, then parsed into MatchRuleConfig[].\n // webpieces-disable no-any-unknown -- opaque match-rules JSON, validated by validateMatchRulesSection\n 'match-rules'?: unknown;\n rulesDir?: string[];\n // webpieces-disable no-any-unknown -- DEPRECATED top-level pr-gate, migrated under `commands`\n 'pr-gate'?: unknown;\n}\n\n/**\n * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.\n */\nexport function findConfigFile(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n const primary = path.join(dir, CONFIG_FILENAME);\n if (fs.existsSync(primary)) return primary;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError.\n */\nexport function readRawConfig(configPath: string): RawConfigFile {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as RawConfigFile;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `webpieces.config.json has invalid JSON — fix the file, then retry.\\n` +\n `Parse error: ${error.message}\\n` +\n `File: ${configPath}`,\n );\n }\n}\n"]}
|
package/src/diff-scope.d.ts
CHANGED
|
@@ -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
|
});
|
package/src/diff-scope.js.map
CHANGED
|
@@ -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"]}
|
package/src/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export { ExcludePaths } from './exclude-hook-paths';
|
|
|
8
8
|
export { isPathExcluded } from './exclude-paths';
|
|
9
9
|
export { defaultRules, defaultRulesDir } from './default-rules';
|
|
10
10
|
export { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';
|
|
11
|
-
export { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, allRuleNames } from './validate-config';
|
|
11
|
+
export { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';
|
|
12
|
+
export { MatchRuleConfig, MatchRuleViolation, findMatchRuleViolations, isMatchRuleAllowedPath, compileMatchRulePatterns, renderMatchRuleMessage, DEFAULT_MATCH_RULES, } from './match-rules-config';
|
|
12
13
|
export type { ConfigSection } from './sections';
|
|
13
14
|
export { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';
|
|
14
15
|
export { FieldDef } from './field-def';
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.writeMainSyncStatus = void 0;
|
|
3
|
+
exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.isNewOrModified = exports.hasChangesInRange = exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = exports.DEFAULT_MATCH_RULES = exports.renderMatchRuleMessage = exports.compileMatchRulePatterns = exports.isMatchRuleAllowedPath = exports.findMatchRuleViolations = exports.MatchRuleViolation = exports.MatchRuleConfig = exports.allRuleNames = exports.validateMatchRulesSection = exports.validateExcludePaths = exports.validateCommandsSection = exports.validateSectionPlacement = exports.validatePrGateSection = exports.validateWebpiecesConfig = exports.writeTemplate = exports.writeTemplateIfMissing = exports.loadTemplate = exports.defaultRulesDir = exports.defaultRules = exports.isPathExcluded = exports.ExcludePaths = exports.CONFIG_FILENAME = exports.findConfigFile = exports.LoadedConfig = exports.loadAndValidate = exports.toError = exports.RuleFailError = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
|
|
4
|
+
exports.prDirFor = exports.loadReviewJson = exports.ReviewJson = exports.buildPrGateConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.PrGateConfig = exports.GateDefinition = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.FeatureBranchGuardConfig = exports.RedirectHowToMergeMainConfig = exports.PrMergeGuardConfig = exports.MergeInProgressGuardConfig = exports.PrCreationGuardConfig = exports.BranchCreationGuardConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_INFO_DIR = void 0;
|
|
5
|
+
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.writeMainSyncStatus = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncLock = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = void 0;
|
|
6
6
|
var types_1 = require("./types");
|
|
7
7
|
Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
|
|
8
8
|
Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
|
|
@@ -35,7 +35,16 @@ Object.defineProperty(exports, "validatePrGateSection", { enumerable: true, get:
|
|
|
35
35
|
Object.defineProperty(exports, "validateSectionPlacement", { enumerable: true, get: function () { return validate_config_1.validateSectionPlacement; } });
|
|
36
36
|
Object.defineProperty(exports, "validateCommandsSection", { enumerable: true, get: function () { return validate_config_1.validateCommandsSection; } });
|
|
37
37
|
Object.defineProperty(exports, "validateExcludePaths", { enumerable: true, get: function () { return validate_config_1.validateExcludePaths; } });
|
|
38
|
+
Object.defineProperty(exports, "validateMatchRulesSection", { enumerable: true, get: function () { return validate_config_1.validateMatchRulesSection; } });
|
|
38
39
|
Object.defineProperty(exports, "allRuleNames", { enumerable: true, get: function () { return validate_config_1.allRuleNames; } });
|
|
40
|
+
var match_rules_config_1 = require("./match-rules-config");
|
|
41
|
+
Object.defineProperty(exports, "MatchRuleConfig", { enumerable: true, get: function () { return match_rules_config_1.MatchRuleConfig; } });
|
|
42
|
+
Object.defineProperty(exports, "MatchRuleViolation", { enumerable: true, get: function () { return match_rules_config_1.MatchRuleViolation; } });
|
|
43
|
+
Object.defineProperty(exports, "findMatchRuleViolations", { enumerable: true, get: function () { return match_rules_config_1.findMatchRuleViolations; } });
|
|
44
|
+
Object.defineProperty(exports, "isMatchRuleAllowedPath", { enumerable: true, get: function () { return match_rules_config_1.isMatchRuleAllowedPath; } });
|
|
45
|
+
Object.defineProperty(exports, "compileMatchRulePatterns", { enumerable: true, get: function () { return match_rules_config_1.compileMatchRulePatterns; } });
|
|
46
|
+
Object.defineProperty(exports, "renderMatchRuleMessage", { enumerable: true, get: function () { return match_rules_config_1.renderMatchRuleMessage; } });
|
|
47
|
+
Object.defineProperty(exports, "DEFAULT_MATCH_RULES", { enumerable: true, get: function () { return match_rules_config_1.DEFAULT_MATCH_RULES; } });
|
|
39
48
|
var sections_1 = require("./sections");
|
|
40
49
|
Object.defineProperty(exports, "HOOK_GUARD_NAMES", { enumerable: true, get: function () { return sections_1.HOOK_GUARD_NAMES; } });
|
|
41
50
|
Object.defineProperty(exports, "isHookGuard", { enumerable: true, get: function () { return sections_1.isHookGuard; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":";;;;;AAAA,iCAA0E;AAAjE,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAC3C,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAChB,6CAA8D;AAArD,8GAAA,eAAe,OAAA;AAAE,2GAAA,YAAY,OAAA;AACtC,6CAAgE;AAAvD,6GAAA,cAAc,OAAA;AAAE,8GAAA,eAAe,OAAA;AACxC,2DAAoD;AAA3C,kHAAA,YAAY,OAAA;AACrB,iDAAiD;AAAxC,+GAAA,cAAc,OAAA;AACvB,iDAAgE;AAAvD,6GAAA,YAAY,OAAA;AAAE,gHAAA,eAAe,OAAA;AACtC,iDAAsF;AAA7E,6GAAA,YAAY,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AAAE,8GAAA,aAAa,OAAA;AAC5D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":";;;;;AAAA,iCAA0E;AAAjE,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAC3C,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAChB,6CAA8D;AAArD,8GAAA,eAAe,OAAA;AAAE,2GAAA,YAAY,OAAA;AACtC,6CAAgE;AAAvD,6GAAA,cAAc,OAAA;AAAE,8GAAA,eAAe,OAAA;AACxC,2DAAoD;AAA3C,kHAAA,YAAY,OAAA;AACrB,iDAAiD;AAAxC,+GAAA,cAAc,OAAA;AACvB,iDAAgE;AAAvD,6GAAA,YAAY,OAAA;AAAE,gHAAA,eAAe,OAAA;AACtC,iDAAsF;AAA7E,6GAAA,YAAY,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AAAE,8GAAA,aAAa,OAAA;AAC5D,qDAAqM;AAA5L,0HAAA,uBAAuB,OAAA;AAAE,wHAAA,qBAAqB,OAAA;AAAE,2HAAA,wBAAwB,OAAA;AAAE,0HAAA,uBAAuB,OAAA;AAAE,uHAAA,oBAAoB,OAAA;AAAE,4HAAA,yBAAyB,OAAA;AAAE,+GAAA,YAAY,OAAA;AACzK,2DAQ8B;AAP1B,qHAAA,eAAe,OAAA;AACf,wHAAA,kBAAkB,OAAA;AAClB,6HAAA,uBAAuB,OAAA;AACvB,4HAAA,sBAAsB,OAAA;AACtB,8HAAA,wBAAwB,OAAA;AACxB,4HAAA,sBAAsB,OAAA;AACtB,yHAAA,mBAAmB,OAAA;AAGvB,uCAA2E;AAAlE,4GAAA,gBAAgB,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AACtD,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA;AAEjB,yCAA+D;AAAtD,2GAAA,cAAc,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAEzC,2CASsB;AARlB,wGAAA,UAAU,OAAA;AACV,yGAAA,WAAW,OAAA;AACX,6GAAA,eAAe,OAAA;AACf,yGAAA,WAAW,OAAA;AACX,mHAAA,qBAAqB,OAAA;AACrB,2HAAA,6BAA6B,OAAA;AAC7B,+GAAA,iBAAiB,OAAA;AACjB,6GAAA,eAAe,OAAA;AAEnB,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,yCASqB;AARjB,8GAAA,iBAAiB,OAAA;AACjB,uGAAA,UAAU,OAAA;AACV,uGAAA,UAAU,OAAA;AACV,8GAAA,iBAAiB,OAAA;AACjB,2GAAA,cAAc,OAAA;AACd,wGAAA,WAAW,OAAA;AACX,mHAAA,sBAAsB,OAAA;AACtB,mHAAA,sBAAsB,OAAA;AAE1B,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,+CA2BwB;AA1BpB,oHAAA,oBAAoB,OAAA;AACpB,kHAAA,kBAAkB,OAAA;AAClB,uHAAA,uBAAuB,OAAA;AACvB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,mHAAA,mBAAmB,OAAA;AACnB,wHAAA,wBAAwB,OAAA;AACxB,qHAAA,qBAAqB,OAAA;AACrB,mHAAA,mBAAmB,OAAA;AACnB,2HAAA,2BAA2B,OAAA;AAC3B,uHAAA,uBAAuB,OAAA;AACvB,wHAAA,wBAAwB,OAAA;AACxB,kIAAA,kCAAkC,OAAA;AAClC,sHAAA,sBAAsB,OAAA;AACtB,yHAAA,yBAAyB,OAAA;AACzB,qHAAA,qBAAqB,OAAA;AACrB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,4HAAA,4BAA4B,OAAA;AAC5B,wHAAA,wBAAwB,OAAA;AACxB,wHAAA,wBAAwB,OAAA;AACxB,yHAAA,yBAAyB,OAAA;AACzB,8GAAA,cAAc,OAAA;AACd,+GAAA,eAAe,OAAA;AACf,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAElB,wFAAwF;AACxF,+CAawB;AAZpB,kHAAA,kBAAkB,OAAA;AAClB,gHAAA,gBAAgB,OAAA;AAChB,iHAAA,iBAAiB,OAAA;AACjB,iHAAA,iBAAiB,OAAA;AACjB,mHAAA,mBAAmB,OAAA;AACnB,iHAAA,iBAAiB,OAAA;AACjB,sHAAA,sBAAsB,OAAA;AACtB,yHAAA,yBAAyB,OAAA;AACzB,iHAAA,iBAAiB,OAAA;AACjB,4GAAA,YAAY,OAAA;AACZ,gHAAA,gBAAgB,OAAA;AAChB,iHAAA,iBAAiB,OAAA;AAgBrB,mDAM0B;AALtB,gHAAA,cAAc,OAAA;AACd,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,qHAAA,mBAAmB,OAAA;AACnB,mHAAA,iBAAiB,OAAA;AAErB,6CAMuB;AALnB,yGAAA,UAAU,OAAA;AACV,6GAAA,cAAc,OAAA;AACd,uGAAA,QAAQ,OAAA;AACR,6GAAA,cAAc,OAAA;AACd,mHAAA,oBAAoB,OAAA;AAExB,uDAiB4B;AAhBxB,kHAAA,cAAc,OAAA;AACd,gHAAA,YAAY,OAAA;AACZ,gIAAA,4BAA4B,OAAA;AAC5B,sHAAA,kBAAkB,OAAA;AAClB,oHAAA,gBAAgB,OAAA;AAChB,sHAAA,kBAAkB,OAAA;AAClB,uHAAA,mBAAmB,OAAA;AACnB,oHAAA,gBAAgB,OAAA;AAChB,qHAAA,iBAAiB,OAAA;AACjB,+GAAA,WAAW,OAAA;AACX,uHAAA,mBAAmB,OAAA;AACnB,iHAAA,aAAa,OAAA;AACb,gHAAA,YAAY,OAAA;AACZ,yHAAA,qBAAqB,OAAA;AACrB,4HAAA,wBAAwB,OAAA;AACxB,uHAAA,mBAAmB,OAAA;AAEvB,qDAK2B;AAJvB,iHAAA,cAAc,OAAA;AACd,sHAAA,mBAAmB,OAAA;AACnB,4HAAA,yBAAyB,OAAA;AACzB,iIAAA,8BAA8B,OAAA","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError } from './rule-fail-error';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME } from './config-file';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_INFO_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n FeatureBranchGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n defaultGates,\n defaultPrGateConfig,\n buildPrGateConfig,\n} from './pr-gate-config';\nexport {\n ReviewJson,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncLock,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n writeMainSyncStatus,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
package/src/load-config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { CommandsConfig } from './commands-config';
|
|
|
2
2
|
import { ExcludePaths } from './exclude-hook-paths';
|
|
3
3
|
import { PrGateConfig } from './pr-gate-config';
|
|
4
4
|
import { ResolvedConfig } from './types';
|
|
5
|
+
import { MatchRuleConfig } from './match-rules-config';
|
|
5
6
|
import { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
|
|
6
7
|
/**
|
|
7
8
|
* Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE
|
|
@@ -11,6 +12,7 @@ import { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
|
|
|
11
12
|
* - `commands` — the `commands` section (gated commands + pr-gate).
|
|
12
13
|
* - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
|
|
13
14
|
* - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).
|
|
15
|
+
* - `matchRules` — the required `match-rules` array (client-authored content guards).
|
|
14
16
|
* - `configPath` — absolute path, or null when no config file was found.
|
|
15
17
|
*/
|
|
16
18
|
export declare class LoadedConfig {
|
|
@@ -19,8 +21,9 @@ export declare class LoadedConfig {
|
|
|
19
21
|
readonly commands: CommandsConfig;
|
|
20
22
|
readonly prGate: PrGateConfig;
|
|
21
23
|
readonly excludePaths: ExcludePaths;
|
|
24
|
+
readonly matchRules: readonly MatchRuleConfig[];
|
|
22
25
|
readonly configPath: string | null;
|
|
23
|
-
constructor(resolved: ResolvedConfig, rulesConfig: WebpiecesRulesConfig, commands: CommandsConfig, prGate: PrGateConfig, excludePaths: ExcludePaths, configPath: string | null);
|
|
26
|
+
constructor(resolved: ResolvedConfig, rulesConfig: WebpiecesRulesConfig, commands: CommandsConfig, prGate: PrGateConfig, excludePaths: ExcludePaths, matchRules: readonly MatchRuleConfig[], configPath: string | null);
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,
|
package/src/load-config.js
CHANGED
|
@@ -57,6 +57,15 @@ function parseExcludePaths(raw) {
|
|
|
57
57
|
const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];
|
|
58
58
|
return new exclude_hook_paths_1.ExcludePaths(rules, guards);
|
|
59
59
|
}
|
|
60
|
+
// Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. Each entry is a
|
|
61
|
+
// plain object from JSON; the engines consume its fields only (no methods), so a structural cast is
|
|
62
|
+
// sufficient. Defensive [] keeps this total even though validateMatchRulesSection ran first.
|
|
63
|
+
// webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig
|
|
64
|
+
function parseMatchRules(raw) {
|
|
65
|
+
if (!Array.isArray(raw))
|
|
66
|
+
return [];
|
|
67
|
+
return raw;
|
|
68
|
+
}
|
|
60
69
|
function buildWebpiecesRulesConfig(
|
|
61
70
|
// webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields
|
|
62
71
|
rawRules, rulesDir) {
|
|
@@ -76,6 +85,7 @@ rawRules, rulesDir) {
|
|
|
76
85
|
* - `commands` — the `commands` section (gated commands + pr-gate).
|
|
77
86
|
* - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
|
|
78
87
|
* - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).
|
|
88
|
+
* - `matchRules` — the required `match-rules` array (client-authored content guards).
|
|
79
89
|
* - `configPath` — absolute path, or null when no config file was found.
|
|
80
90
|
*/
|
|
81
91
|
class LoadedConfig {
|
|
@@ -84,13 +94,16 @@ class LoadedConfig {
|
|
|
84
94
|
commands;
|
|
85
95
|
prGate;
|
|
86
96
|
excludePaths;
|
|
97
|
+
matchRules;
|
|
87
98
|
configPath;
|
|
88
|
-
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
100
|
+
constructor(resolved, rulesConfig, commands, prGate, excludePaths, matchRules, configPath) {
|
|
89
101
|
this.resolved = resolved;
|
|
90
102
|
this.rulesConfig = rulesConfig;
|
|
91
103
|
this.commands = commands;
|
|
92
104
|
this.prGate = prGate;
|
|
93
105
|
this.excludePaths = excludePaths;
|
|
106
|
+
this.matchRules = matchRules;
|
|
94
107
|
this.configPath = configPath;
|
|
95
108
|
}
|
|
96
109
|
}
|
|
@@ -119,7 +132,7 @@ function loadAndValidate(cwd) {
|
|
|
119
132
|
const configPath = (0, config_file_1.findConfigFile)(cwd);
|
|
120
133
|
if (!configPath) {
|
|
121
134
|
const emptyCommands = (0, commands_config_1.buildCommandsConfig)(undefined);
|
|
122
|
-
return new LoadedConfig(new types_1.ResolvedConfig(new Map(), new Set(), [], null), new WebpiecesRulesConfig_1.WebpiecesRulesConfig(), emptyCommands, emptyCommands.prGate, new exclude_hook_paths_1.ExcludePaths([], []), null);
|
|
135
|
+
return new LoadedConfig(new types_1.ResolvedConfig(new Map(), new Set(), [], null), new WebpiecesRulesConfig_1.WebpiecesRulesConfig(), emptyCommands, emptyCommands.prGate, new exclude_hook_paths_1.ExcludePaths([], []), [], null);
|
|
123
136
|
}
|
|
124
137
|
const consumerConfig = (0, config_file_1.readRawConfig)(configPath);
|
|
125
138
|
const rulesSection = normalizeDeprecatedKeys(consumerConfig.rules || {});
|
|
@@ -137,6 +150,7 @@ function loadAndValidate(cwd) {
|
|
|
137
150
|
...(0, validate_config_1.validateSectionPlacement)(rulesSection, hookGuardsSection),
|
|
138
151
|
...(0, validate_config_1.validateCommandsSection)(consumerConfig.commands, legacyPrGate),
|
|
139
152
|
...(0, validate_config_1.validateExcludePaths)(consumerConfig.excludePaths),
|
|
153
|
+
...(0, validate_config_1.validateMatchRulesSection)(consumerConfig['match-rules']),
|
|
140
154
|
];
|
|
141
155
|
if (errors.length > 0) {
|
|
142
156
|
throw new inform_ai_error_1.InformAiError(`webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\n\n` +
|
|
@@ -156,6 +170,7 @@ function loadAndValidate(cwd) {
|
|
|
156
170
|
const resolved = new types_1.ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);
|
|
157
171
|
const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);
|
|
158
172
|
const excludePaths = parseExcludePaths(consumerConfig.excludePaths);
|
|
159
|
-
|
|
173
|
+
const matchRules = parseMatchRules(consumerConfig['match-rules']);
|
|
174
|
+
return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);
|
|
160
175
|
}
|
|
161
176
|
//# sourceMappingURL=load-config.js.map
|
package/src/load-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { findConfigFile, readRawConfig } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n// Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n// that surface them in their fix hints, so a project renames a command in one place. Only fills a\n// gap — an explicit per-guard override wins. Mutates the merged guard entries in place.\nfunction applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n): void {\n const prCreation = rules['pr-creation-guard'];\n if (prCreation && prCreation['upsertPrCommand'] === undefined) {\n prCreation['upsertPrCommand'] = commands.upsertPr;\n }\n const mergeInProgress = rules['merge-in-progress-guard'];\n if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {\n mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;\n }\n}\n\n// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n}\n\n// Parse the (already-validated) raw excludePaths block into the typed ExcludePaths. Defensive\n// defaults keep this total even though validateExcludePaths guarantees both string[] lists are set.\n// webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\nfunction parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n}\n\nfunction buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n}\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data):\n * - `resolved` — Map-based view merged with defaultRules (nx executors).\n * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.\n * - `commands` — the `commands` section (gated commands + pr-gate).\n * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).\n * - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).\n * - `configPath` — absolute path, or null when no config file was found.\n */\nexport class LoadedConfig {\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\n// The old key stays accepted indefinitely; teams can flip to the new name whenever convenient.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\nfunction normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n}\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`\n * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no\n * config file is found it returns lenient empties/defaults (matching prior no-file behavior).\n */\nexport function loadAndValidate(cwd: string): LoadedConfig {\n const configPath = findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n new ExcludePaths([], []),\n null,\n );\n }\n\n const consumerConfig = readRawConfig(configPath);\n const rulesSection = normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches by\n // each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n // A non-empty rulesDir means custom rules exist (loaded at runtime by ai-hook-rules), so a config\n // key with no built-in schema may be a legitimate custom rule. With no rulesDir, an unknown key is\n // a dead/typo'd entry and is rejected (validateWebpiecesConfig).\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ];\n if (errors.length > 0) {\n throw new InformAiError(\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n'),\n );\n }\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = parseExcludePaths(consumerConfig.excludePaths);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, configPath);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":";;;AAmIA,0CA6DC;AAhMD,uDAAwE;AACxE,+CAA8D;AAC9D,mDAA+C;AAC/C,6DAAoD;AACpD,uDAAkD;AAElD,mCAA0E;AAC1E,uDAAgK;AAEhK,iEAA8D;AAE9D,mGAAmG;AACnG,kGAAkG;AAClG,wFAAwF;AACxF,SAAS,oBAAoB;AACzB,mEAAmE;AACnE,KAA8C,EAC9C,QAAwB;IAExB,MAAM,UAAU,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAC9C,IAAI,UAAU,IAAI,UAAU,CAAC,iBAAiB,CAAC,KAAK,SAAS,EAAE,CAAC;QAC5D,UAAU,CAAC,iBAAiB,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;IACtD,CAAC;IACD,MAAM,eAAe,GAAG,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACzD,IAAI,eAAe,IAAI,eAAe,CAAC,sBAAsB,CAAC,KAAK,SAAS,EAAE,CAAC;QAC3E,eAAe,CAAC,sBAAsB,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC;IACrE,CAAC;AACL,CAAC;AAED,kFAAkF;AAClF,SAAS,SAAS;AACd,wDAAwD;AACxD,QAA6C;AAC7C,wDAAwD;AACxD,YAAiD;IAEjD,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,0BAAkB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,0BAAkB,CAAC,YAA4B,CAAC,CAAC;IAC3E,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,0BAAkB,CAAC,QAAuB,CAAC,CAAC;IAE1E,iEAAiE;IACjE,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC7E,OAAO,IAAI,0BAAkB,CAAC,MAAqB,CAAC,CAAC;AACzD,CAAC;AAED,8FAA8F;AAC9F,oGAAoG;AACpG,wFAAwF;AACxF,SAAS,iBAAiB,CAAC,GAAY;IACnC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACnG,4FAA4F;IAC5F,MAAM,CAAC,GAAG,GAA+B,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7F,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,OAAO,IAAI,iCAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,oGAAoG;AACpG,oGAAoG;AACpG,6FAA6F;AAC7F,oGAAoG;AACpG,SAAS,eAAe,CAAC,GAAY;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,GAAwB,CAAC;AACpC,CAAC;AAED,SAAS,yBAAyB;AAC9B,4FAA4F;AAC5F,QAAiD,EACjD,QAAkB;IAElB,MAAM,KAAK,GAAG,IAAI,2CAAoB,EAAE,CAAC;IACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,4EAA4E;QAC3E,KAAiC,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAa,YAAY;IAGR;IACA;IACA;IACA;IACA;IACA;IACA;IARb,yDAAyD;IACzD,YACa,QAAwB,EACxB,WAAiC,EACjC,QAAwB,EACxB,MAAoB,EACpB,YAA0B,EAC1B,UAAsC,EACtC,UAAyB;QANzB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,gBAAW,GAAX,WAAW,CAAsB;QACjC,aAAQ,GAAR,QAAQ,CAAgB;QACxB,WAAM,GAAN,MAAM,CAAc;QACpB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAA4B;QACtC,eAAU,GAAV,UAAU,CAAe;IACnC,CAAC;CACP;AAXD,oCAWC;AAED,qGAAqG;AACrG,oGAAoG;AACpG,qGAAqG;AACrG,+FAA+F;AAC/F,MAAM,uBAAuB,GAAqC;IAC9D,kBAAkB,EAAE,gBAAgB;CACvC,CAAC;AAKF,SAAS,uBAAuB,CAAC,OAAuB;IACpD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,GAAW;IACvC,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,MAAM,aAAa,GAAG,IAAA,qCAAmB,EAAC,SAAS,CAAC,CAAC;QACrD,OAAO,IAAI,YAAY,CACnB,IAAI,sBAAc,CAAC,IAAI,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAClD,IAAI,2CAAoB,EAAE,EAC1B,aAAa,EACb,aAAa,CAAC,MAAM,EACpB,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,EACxB,EAAE,EACF,IAAI,CACP,CAAC;IACN,CAAC;IAED,MAAM,cAAc,GAAG,IAAA,2BAAa,EAAC,UAAU,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,uBAAuB,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,cAAc,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IACnF,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAE/C,iGAAiG;IACjG,gGAAgG;IAChG,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAEhE,kGAAkG;IAClG,mGAAmG;IACnG,iEAAiE;IACjE,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,IAAI,EAAE,CAAC;IAE/C,MAAM,MAAM,GAAG;QACX,GAAG,IAAA,yCAAuB,EAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,GAAG,IAAA,0CAAwB,EAAC,YAAY,EAAE,iBAAiB,CAAC;QAC5D,GAAG,IAAA,yCAAuB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC;QACjE,GAAG,IAAA,sCAAoB,EAAC,cAAc,CAAC,YAAY,CAAC;QACpD,GAAG,IAAA,2CAAyB,EAAC,cAAc,CAAC,aAAa,CAAC,CAAC;KAC9D,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,+BAAa,CACnB,6BAA6B,MAAM,CAAC,MAAM,iDAAiD;YAC3F,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACzC,CAAC;IACN,CAAC;IACD,MAAM,QAAQ,GAAG,IAAA,qCAAmB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC5E,oBAAoB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAE9C,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IACpE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC1D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;QACzB,GAAG,MAAM,CAAC,IAAI,CAAC,4BAAY,CAAC;QAC5B,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;KAChC,CAAC,CAAC;IACH,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAC9B,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,4BAAY,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,sBAAc,CAAC,WAAW,EAAE,uBAAuB,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IAEhG,MAAM,WAAW,GAAG,yBAAyB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,iBAAiB,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,eAAe,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IAElE,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AACpH,CAAC","sourcesContent":["import { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { findConfigFile, readRawConfig } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateMatchRulesSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { MatchRuleConfig } from './match-rules-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n// Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n// that surface them in their fix hints, so a project renames a command in one place. Only fills a\n// gap — an explicit per-guard override wins. Mutates the merged guard entries in place.\nfunction applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n): void {\n const prCreation = rules['pr-creation-guard'];\n if (prCreation && prCreation['upsertPrCommand'] === undefined) {\n prCreation['upsertPrCommand'] = commands.upsertPr;\n }\n const mergeInProgress = rules['merge-in-progress-guard'];\n if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {\n mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;\n }\n}\n\n// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n}\n\n// Parse the (already-validated) raw excludePaths block into the typed ExcludePaths. Defensive\n// defaults keep this total even though validateExcludePaths guarantees both string[] lists are set.\n// webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\nfunction parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n}\n\n// Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. Each entry is a\n// plain object from JSON; the engines consume its fields only (no methods), so a structural cast is\n// sufficient. Defensive [] keeps this total even though validateMatchRulesSection ran first.\n// webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\nfunction parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n return raw as MatchRuleConfig[];\n}\n\nfunction buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n}\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data):\n * - `resolved` — Map-based view merged with defaultRules (nx executors).\n * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.\n * - `commands` — the `commands` section (gated commands + pr-gate).\n * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).\n * - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).\n * - `matchRules` — the required `match-rules` array (client-authored content guards).\n * - `configPath` — absolute path, or null when no config file was found.\n */\nexport class LoadedConfig {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly matchRules: readonly MatchRuleConfig[],\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\n// The old key stays accepted indefinitely; teams can flip to the new name whenever convenient.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\nfunction normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n}\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`\n * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no\n * config file is found it returns lenient empties/defaults (matching prior no-file behavior).\n */\nexport function loadAndValidate(cwd: string): LoadedConfig {\n const configPath = findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = readRawConfig(configPath);\n const rulesSection = normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches by\n // each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n // A non-empty rulesDir means custom rules exist (loaded at runtime by ai-hook-rules), so a config\n // key with no built-in schema may be a legitimate custom rule. With no rulesDir, an unknown key is\n // a dead/typo'd entry and is rejected (validateWebpiecesConfig).\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n'),\n );\n }\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseRuleConfig, ModifiedCodeMode } from './rule-configs';
|
|
2
|
+
/**
|
|
3
|
+
* One entry of the `match-rules` array. Extends BaseRuleConfig so AbstractRule.shouldRun()
|
|
4
|
+
* (OFF / epoch / branch escape hatches) works per entry. `name` doubles as the
|
|
5
|
+
* `// webpieces-disable <name> -- <reason>` token and the report label.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MatchRuleConfig extends BaseRuleConfig {
|
|
8
|
+
mode?: ModifiedCodeMode;
|
|
9
|
+
name: string;
|
|
10
|
+
patterns: string[];
|
|
11
|
+
mainMessage: string;
|
|
12
|
+
options: string[];
|
|
13
|
+
disableAllowed?: boolean;
|
|
14
|
+
allowedPaths?: string[];
|
|
15
|
+
constructor(name: string, patterns: string[], mainMessage: string, mode: ModifiedCodeMode, ignoreModifiedUntilEpoch: number, options?: string[], disableAllowed?: boolean, allowedPaths?: string[]);
|
|
16
|
+
}
|
|
17
|
+
/** One flagged line. Raw hit — callers apply their own disable filtering. */
|
|
18
|
+
export declare class MatchRuleViolation {
|
|
19
|
+
readonly line: number;
|
|
20
|
+
readonly context: string;
|
|
21
|
+
readonly patternIndex: number;
|
|
22
|
+
constructor(line: number, context: string, patternIndex: number);
|
|
23
|
+
}
|
|
24
|
+
/** A file is exempt if it's a test file or matches one of the rule's allowedPaths globs. */
|
|
25
|
+
export declare function isMatchRuleAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Compile a match-rule's raw-regex patterns. Patterns are trusted here — validateMatchRulesSection
|
|
28
|
+
* (run in loadAndValidate, before any engine consumes a match-rule) compile-checks every pattern and
|
|
29
|
+
* rejects the config on the first bad one, so an invalid pattern can never reach this function.
|
|
30
|
+
*/
|
|
31
|
+
export declare function compileMatchRulePatterns(config: MatchRuleConfig): RegExp[];
|
|
32
|
+
/**
|
|
33
|
+
* Pure matching engine shared by BOTH engines. Takes the ORIGINAL lines (each is stripped of its
|
|
34
|
+
* `//` comment before matching, mirroring the code-rules validators), returns raw hits — NO disable
|
|
35
|
+
* filtering (ai-hook applies its line-mapped isLineDisabled; code-rules applies hasDisable). Returns
|
|
36
|
+
* [] for exempt paths (test files / allowedPaths). At most one violation per line (first pattern wins).
|
|
37
|
+
*/
|
|
38
|
+
export declare function findMatchRuleViolations(lines: readonly string[], relativePath: string, config: MatchRuleConfig): MatchRuleViolation[];
|
|
39
|
+
/**
|
|
40
|
+
* Render a match-rule's message as a single string for the code-rules console report. Mirrors the
|
|
41
|
+
* ai-hook FixHint layout (mainMessage, "Fix Option N:", disable escape) so both engines read alike.
|
|
42
|
+
* ai-hook builds a real FixHint instead (see MatchRule) to reuse report.ts numbering.
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderMatchRuleMessage(config: MatchRuleConfig): string;
|
|
45
|
+
export declare const DEFAULT_MATCH_RULES: readonly MatchRuleConfig[];
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_MATCH_RULES = exports.MatchRuleViolation = exports.MatchRuleConfig = void 0;
|
|
4
|
+
exports.isMatchRuleAllowedPath = isMatchRuleAllowedPath;
|
|
5
|
+
exports.compileMatchRulePatterns = compileMatchRulePatterns;
|
|
6
|
+
exports.findMatchRuleViolations = findMatchRuleViolations;
|
|
7
|
+
exports.renderMatchRuleMessage = renderMatchRuleMessage;
|
|
8
|
+
const rule_configs_1 = require("./rule-configs");
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// match-rules — a generic, client-configurable content-guard engine.
|
|
12
|
+
//
|
|
13
|
+
// Unlike the keyed `rules` (each a framework class with a fixed regex + message),
|
|
14
|
+
// a match-rule is authored ENTIRELY in webpieces.config.json: a `name`, a list of
|
|
15
|
+
// raw-regex `patterns` to flag, a `mainMessage` + `options[]` shown to the AI, and
|
|
16
|
+
// per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `ignoreModifiedUntilEpoch`).
|
|
17
|
+
// The framework ships ONE default example — the `no-fetch` guard (see DEFAULT_MATCH_RULES)
|
|
18
|
+
// — and clients add more (no-moment, no-lodash-chain, …) without a framework release.
|
|
19
|
+
//
|
|
20
|
+
// Lives in a NEW top-level `match-rules` ARRAY section (shaped like `pr-gate`/`excludePaths`),
|
|
21
|
+
// NOT a keyed entry under `rules`. Both engines (ai-hook-rules edit-time, code-rules build-time)
|
|
22
|
+
// instantiate one guard per array entry and share the pure matching engine below.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* One entry of the `match-rules` array. Extends BaseRuleConfig so AbstractRule.shouldRun()
|
|
26
|
+
* (OFF / epoch / branch escape hatches) works per entry. `name` doubles as the
|
|
27
|
+
* `// webpieces-disable <name> -- <reason>` token and the report label.
|
|
28
|
+
*/
|
|
29
|
+
class MatchRuleConfig extends rule_configs_1.BaseRuleConfig {
|
|
30
|
+
name;
|
|
31
|
+
patterns;
|
|
32
|
+
mainMessage;
|
|
33
|
+
options;
|
|
34
|
+
disableAllowed;
|
|
35
|
+
allowedPaths;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
37
|
+
constructor(name, patterns, mainMessage, mode, ignoreModifiedUntilEpoch, options = [], disableAllowed = true, allowedPaths = []) {
|
|
38
|
+
super();
|
|
39
|
+
this.name = name;
|
|
40
|
+
this.patterns = patterns;
|
|
41
|
+
this.mainMessage = mainMessage;
|
|
42
|
+
this.mode = mode;
|
|
43
|
+
this.ignoreModifiedUntilEpoch = ignoreModifiedUntilEpoch;
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.disableAllowed = disableAllowed;
|
|
46
|
+
this.allowedPaths = allowedPaths;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.MatchRuleConfig = MatchRuleConfig;
|
|
50
|
+
/** One flagged line. Raw hit — callers apply their own disable filtering. */
|
|
51
|
+
class MatchRuleViolation {
|
|
52
|
+
line;
|
|
53
|
+
context;
|
|
54
|
+
patternIndex;
|
|
55
|
+
constructor(line, context, patternIndex) {
|
|
56
|
+
this.line = line;
|
|
57
|
+
this.context = context;
|
|
58
|
+
this.patternIndex = patternIndex;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.MatchRuleViolation = MatchRuleViolation;
|
|
62
|
+
const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
|
|
63
|
+
// Glob → RegExp, matching the convention used by no-symbol-di-tokens (`**` spans path
|
|
64
|
+
// separators, `*` stays within a segment). Kept local so rules-config owns no shared glob util.
|
|
65
|
+
function globToRegex(pattern) {
|
|
66
|
+
let re = '';
|
|
67
|
+
let i = 0;
|
|
68
|
+
while (i < pattern.length) {
|
|
69
|
+
const ch = pattern[i];
|
|
70
|
+
if (ch === '*') {
|
|
71
|
+
if (pattern[i + 1] === '*') {
|
|
72
|
+
re += '.*';
|
|
73
|
+
i += 2;
|
|
74
|
+
if (pattern[i] === '/')
|
|
75
|
+
i += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
re += '[^/]*';
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === '?') {
|
|
83
|
+
re += '[^/]';
|
|
84
|
+
i += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if ('.+^$(){}|[]\\'.includes(ch)) {
|
|
88
|
+
re += '\\' + ch;
|
|
89
|
+
i += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
re += ch;
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
return new RegExp('^' + re + '$');
|
|
96
|
+
}
|
|
97
|
+
/** A file is exempt if it's a test file or matches one of the rule's allowedPaths globs. */
|
|
98
|
+
function isMatchRuleAllowedPath(relativePath, allowedPaths) {
|
|
99
|
+
if (TEST_PATHS.some((re) => re.test(relativePath)))
|
|
100
|
+
return true;
|
|
101
|
+
return allowedPaths.some((pattern) => globToRegex(pattern).test(relativePath));
|
|
102
|
+
}
|
|
103
|
+
function stripLineComment(line) {
|
|
104
|
+
const idx = line.indexOf('//');
|
|
105
|
+
if (idx === -1)
|
|
106
|
+
return line;
|
|
107
|
+
return line.substring(0, idx);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compile a match-rule's raw-regex patterns. Patterns are trusted here — validateMatchRulesSection
|
|
111
|
+
* (run in loadAndValidate, before any engine consumes a match-rule) compile-checks every pattern and
|
|
112
|
+
* rejects the config on the first bad one, so an invalid pattern can never reach this function.
|
|
113
|
+
*/
|
|
114
|
+
function compileMatchRulePatterns(config) {
|
|
115
|
+
return (config.patterns ?? []).map((pattern) => new RegExp(pattern));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Pure matching engine shared by BOTH engines. Takes the ORIGINAL lines (each is stripped of its
|
|
119
|
+
* `//` comment before matching, mirroring the code-rules validators), returns raw hits — NO disable
|
|
120
|
+
* filtering (ai-hook applies its line-mapped isLineDisabled; code-rules applies hasDisable). Returns
|
|
121
|
+
* [] for exempt paths (test files / allowedPaths). At most one violation per line (first pattern wins).
|
|
122
|
+
*/
|
|
123
|
+
function findMatchRuleViolations(lines, relativePath, config) {
|
|
124
|
+
if (isMatchRuleAllowedPath(relativePath, config.allowedPaths ?? []))
|
|
125
|
+
return [];
|
|
126
|
+
const compiled = compileMatchRulePatterns(config);
|
|
127
|
+
if (compiled.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
const violations = [];
|
|
130
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
131
|
+
const stripped = stripLineComment(lines[i] ?? '');
|
|
132
|
+
for (let p = 0; p < compiled.length; p += 1) {
|
|
133
|
+
if (compiled[p].test(stripped)) {
|
|
134
|
+
violations.push(new MatchRuleViolation(i + 1, (lines[i] ?? '').trim(), p));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return violations;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Render a match-rule's message as a single string for the code-rules console report. Mirrors the
|
|
143
|
+
* ai-hook FixHint layout (mainMessage, "Fix Option N:", disable escape) so both engines read alike.
|
|
144
|
+
* ai-hook builds a real FixHint instead (see MatchRule) to reuse report.ts numbering.
|
|
145
|
+
*/
|
|
146
|
+
function renderMatchRuleMessage(config) {
|
|
147
|
+
const lines = [config.mainMessage];
|
|
148
|
+
(config.options ?? []).forEach((opt, i) => {
|
|
149
|
+
lines.push(` Fix Option ${String(i + 1)}: ${opt}`);
|
|
150
|
+
});
|
|
151
|
+
if (config.disableAllowed ?? true) {
|
|
152
|
+
lines.push(` Escape (if truly needed): // ${constants_1.WEBPIECES_DISABLE} ${config.name} -- <reason>`);
|
|
153
|
+
}
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
// The ONE guard the framework seeds. Printed verbatim (as JSON) by validateMatchRulesSection when the
|
|
157
|
+
// `match-rules` section is missing, and written into a fresh config by the installer. Clients edit it
|
|
158
|
+
// and add more entries. `packages/http/http-client/**` MUST stay allowlisted — ClientFactory.ts is the
|
|
159
|
+
// single sanctioned fetch (the generated proxy); the apis-external dirs host external-service impls.
|
|
160
|
+
exports.DEFAULT_MATCH_RULES = [
|
|
161
|
+
new MatchRuleConfig('no-fetch', [
|
|
162
|
+
'(?<![.\\w])fetch\\s*\\(',
|
|
163
|
+
'\\baxios\\b',
|
|
164
|
+
'\\bXMLHttpRequest\\b',
|
|
165
|
+
'\\bnew\\s+Request\\s*\\(',
|
|
166
|
+
"from\\s+['\"](node-fetch|got|undici)['\"]",
|
|
167
|
+
], 'Raw HTTP (fetch/axios/XMLHttpRequest/…) bypasses contract-first development — the client and server stop sharing the same API contract/types. Generate a type-safe client from the contract instead:', 'NEW_AND_MODIFIED_CODE', 0, [
|
|
168
|
+
"PREFERRED: generate a client from the API you want to call — import { createApiClient, ClientConfig } from '@webpieces/http-client'; const client = createApiClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);",
|
|
169
|
+
'Reuse an existing API contract already defined in your repo (under libraries/apis/**) and generate its client the same way.',
|
|
170
|
+
'For a truly external service, create a NEW API contract (a decorated abstract class) AND a NEW implementation that calls fetch behind that contract, under an allowlisted dir (libraries/apis-external/**). The contract stays the shared surface; fetch is an impl detail.',
|
|
171
|
+
], true, ['packages/http/http-client/**', 'libraries/apis-external/**', 'libraries/apis/external/**']),
|
|
172
|
+
];
|
|
173
|
+
//# sourceMappingURL=match-rules-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match-rules-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/match-rules-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig, ModifiedCodeMode } from './rule-configs';\nimport { WEBPIECES_DISABLE } from './constants';\n\n// ---------------------------------------------------------------------------\n// match-rules — a generic, client-configurable content-guard engine.\n//\n// Unlike the keyed `rules` (each a framework class with a fixed regex + message),\n// a match-rule is authored ENTIRELY in webpieces.config.json: a `name`, a list of\n// raw-regex `patterns` to flag, a `mainMessage` + `options[]` shown to the AI, and\n// per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `ignoreModifiedUntilEpoch`).\n// The framework ships ONE default example — the `no-fetch` guard (see DEFAULT_MATCH_RULES)\n// — and clients add more (no-moment, no-lodash-chain, …) without a framework release.\n//\n// Lives in a NEW top-level `match-rules` ARRAY section (shaped like `pr-gate`/`excludePaths`),\n// NOT a keyed entry under `rules`. Both engines (ai-hook-rules edit-time, code-rules build-time)\n// instantiate one guard per array entry and share the pure matching engine below.\n// ---------------------------------------------------------------------------\n\n/**\n * One entry of the `match-rules` array. Extends BaseRuleConfig so AbstractRule.shouldRun()\n * (OFF / epoch / branch escape hatches) works per entry. `name` doubles as the\n * `// webpieces-disable <name> -- <reason>` token and the report label.\n */\nexport class MatchRuleConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n name: string;\n patterns: string[];\n mainMessage: string;\n options: string[];\n disableAllowed?: boolean;\n allowedPaths?: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n name: string,\n patterns: string[],\n mainMessage: string,\n mode: ModifiedCodeMode,\n ignoreModifiedUntilEpoch: number,\n options: string[] = [],\n disableAllowed: boolean = true,\n allowedPaths: string[] = [],\n ) {\n super();\n this.name = name;\n this.patterns = patterns;\n this.mainMessage = mainMessage;\n this.mode = mode;\n this.ignoreModifiedUntilEpoch = ignoreModifiedUntilEpoch;\n this.options = options;\n this.disableAllowed = disableAllowed;\n this.allowedPaths = allowedPaths;\n }\n}\n\n/** One flagged line. Raw hit — callers apply their own disable filtering. */\nexport class MatchRuleViolation {\n readonly line: number;\n readonly context: string;\n readonly patternIndex: number;\n\n constructor(line: number, context: string, patternIndex: number) {\n this.line = line;\n this.context = context;\n this.patternIndex = patternIndex;\n }\n}\n\nconst TEST_PATHS: readonly RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\n// Glob → RegExp, matching the convention used by no-symbol-di-tokens (`**` spans path\n// separators, `*` stays within a segment). Kept local so rules-config owns no shared glob util.\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n\n/** A file is exempt if it's a test file or matches one of the rule's allowedPaths globs. */\nexport function isMatchRuleAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nfunction stripLineComment(line: string): string {\n const idx = line.indexOf('//');\n if (idx === -1) return line;\n return line.substring(0, idx);\n}\n\n/**\n * Compile a match-rule's raw-regex patterns. Patterns are trusted here — validateMatchRulesSection\n * (run in loadAndValidate, before any engine consumes a match-rule) compile-checks every pattern and\n * rejects the config on the first bad one, so an invalid pattern can never reach this function.\n */\nexport function compileMatchRulePatterns(config: MatchRuleConfig): RegExp[] {\n return (config.patterns ?? []).map((pattern: string) => new RegExp(pattern));\n}\n\n/**\n * Pure matching engine shared by BOTH engines. Takes the ORIGINAL lines (each is stripped of its\n * `//` comment before matching, mirroring the code-rules validators), returns raw hits — NO disable\n * filtering (ai-hook applies its line-mapped isLineDisabled; code-rules applies hasDisable). Returns\n * [] for exempt paths (test files / allowedPaths). At most one violation per line (first pattern wins).\n */\nexport function findMatchRuleViolations(\n lines: readonly string[],\n relativePath: string,\n config: MatchRuleConfig,\n): MatchRuleViolation[] {\n if (isMatchRuleAllowedPath(relativePath, config.allowedPaths ?? [])) return [];\n\n const compiled = compileMatchRulePatterns(config);\n if (compiled.length === 0) return [];\n\n const violations: MatchRuleViolation[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = stripLineComment(lines[i] ?? '');\n for (let p = 0; p < compiled.length; p += 1) {\n if (compiled[p].test(stripped)) {\n violations.push(new MatchRuleViolation(i + 1, (lines[i] ?? '').trim(), p));\n break;\n }\n }\n }\n return violations;\n}\n\n/**\n * Render a match-rule's message as a single string for the code-rules console report. Mirrors the\n * ai-hook FixHint layout (mainMessage, \"Fix Option N:\", disable escape) so both engines read alike.\n * ai-hook builds a real FixHint instead (see MatchRule) to reuse report.ts numbering.\n */\nexport function renderMatchRuleMessage(config: MatchRuleConfig): string {\n const lines: string[] = [config.mainMessage];\n (config.options ?? []).forEach((opt: string, i: number) => {\n lines.push(` Fix Option ${String(i + 1)}: ${opt}`);\n });\n if (config.disableAllowed ?? true) {\n lines.push(` Escape (if truly needed): // ${WEBPIECES_DISABLE} ${config.name} -- <reason>`);\n }\n return lines.join('\\n');\n}\n\n// The ONE guard the framework seeds. Printed verbatim (as JSON) by validateMatchRulesSection when the\n// `match-rules` section is missing, and written into a fresh config by the installer. Clients edit it\n// and add more entries. `packages/http/http-client/**` MUST stay allowlisted — ClientFactory.ts is the\n// single sanctioned fetch (the generated proxy); the apis-external dirs host external-service impls.\nexport const DEFAULT_MATCH_RULES: readonly MatchRuleConfig[] = [\n new MatchRuleConfig(\n 'no-fetch',\n [\n '(?<![.\\\\w])fetch\\\\s*\\\\(',\n '\\\\baxios\\\\b',\n '\\\\bXMLHttpRequest\\\\b',\n '\\\\bnew\\\\s+Request\\\\s*\\\\(',\n \"from\\\\s+['\\\"](node-fetch|got|undici)['\\\"]\",\n ],\n 'Raw HTTP (fetch/axios/XMLHttpRequest/…) bypasses contract-first development — the client and server stop sharing the same API contract/types. Generate a type-safe client from the contract instead:',\n 'NEW_AND_MODIFIED_CODE',\n 0,\n [\n \"PREFERRED: generate a client from the API you want to call — import { createApiClient, ClientConfig } from '@webpieces/http-client'; const client = createApiClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);\",\n 'Reuse an existing API contract already defined in your repo (under libraries/apis/**) and generate its client the same way.',\n 'For a truly external service, create a NEW API contract (a decorated abstract class) AND a NEW implementation that calls fetch behind that contract, under an allowlisted dir (libraries/apis-external/**). The contract stays the shared surface; fetch is an impl detail.',\n ],\n true,\n ['packages/http/http-client/**', 'libraries/apis-external/**', 'libraries/apis/external/**'],\n ),\n];\n"]}
|
package/src/validate-config.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ export declare function validatePrGateSection(section: unknown): string[];
|
|
|
14
14
|
* friendly errors and never throws — same contract as validatePrGateSection.
|
|
15
15
|
*/
|
|
16
16
|
export declare function validateExcludePaths(section: unknown): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Validate the REQUIRED top-level `match-rules` array (client-authored content guards). MISSING →
|
|
19
|
+
* one error printing the ready-to-paste `no-fetch` example (add at least this; more can follow).
|
|
20
|
+
* Present-but-`[]` → allowed (a conscious opt-out, matching pr-gate mode:OFF / excludePaths []).
|
|
21
|
+
* Otherwise every entry is validated field-by-field (each regex compile-checked) plus name
|
|
22
|
+
* uniqueness. Copy-paste-friendly errors; never throws — same contract as validatePrGateSection.
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateMatchRulesSection(section: unknown): string[];
|
|
17
25
|
/**
|
|
18
26
|
* Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards
|
|
19
27
|
* under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a
|
package/src/validate-config.js
CHANGED
|
@@ -4,38 +4,42 @@ exports.allRuleNames = allRuleNames;
|
|
|
4
4
|
exports.validateWebpiecesConfig = validateWebpiecesConfig;
|
|
5
5
|
exports.validatePrGateSection = validatePrGateSection;
|
|
6
6
|
exports.validateExcludePaths = validateExcludePaths;
|
|
7
|
+
exports.validateMatchRulesSection = validateMatchRulesSection;
|
|
7
8
|
exports.validateSectionPlacement = validateSectionPlacement;
|
|
8
9
|
exports.validateCommandsSection = validateCommandsSection;
|
|
9
10
|
const sections_1 = require("./sections");
|
|
10
11
|
const rule_configs_1 = require("./rule-configs");
|
|
12
|
+
const match_rules_config_1 = require("./match-rules-config");
|
|
13
|
+
const to_error_1 = require("./to-error");
|
|
14
|
+
const rule_configs_2 = require("./rule-configs");
|
|
11
15
|
// Thin lookup table — each entry delegates to the class's own SCHEMA.
|
|
12
16
|
// No field lists here; all schemas live with their config class.
|
|
13
17
|
const RULE_SCHEMAS = {
|
|
14
|
-
'max-method-lines':
|
|
15
|
-
'max-file-lines':
|
|
16
|
-
'require-return-type':
|
|
17
|
-
'no-inline-type-literals':
|
|
18
|
-
'no-any-unknown':
|
|
19
|
-
'no-implicit-any':
|
|
20
|
-
'prisma-validate-dtos':
|
|
21
|
-
'prisma-converter':
|
|
22
|
-
'no-destructure':
|
|
23
|
-
'no-unmanaged-exceptions':
|
|
24
|
-
'catch-error-pattern':
|
|
25
|
-
'throw-cause-required':
|
|
26
|
-
'angular-no-direct-api-in-resolver':
|
|
27
|
-
'no-symbol-di-tokens':
|
|
28
|
-
'branch-creation-guard':
|
|
29
|
-
'pr-creation-guard':
|
|
30
|
-
'merge-in-progress-guard':
|
|
31
|
-
'pr-merge-guard':
|
|
32
|
-
'redirect-how-to-merge-main':
|
|
33
|
-
'feature-branch-guard':
|
|
34
|
-
'no-file-import-cycles':
|
|
35
|
-
'runtime-architecture':
|
|
36
|
-
'nx-wiring':
|
|
37
|
-
'no-js-files':
|
|
38
|
-
'validate-ts-in-src':
|
|
18
|
+
'max-method-lines': rule_configs_2.MaxMethodLinesConfig.SCHEMA,
|
|
19
|
+
'max-file-lines': rule_configs_2.MaxFileLinesConfig.SCHEMA,
|
|
20
|
+
'require-return-type': rule_configs_2.RequireReturnTypeConfig.SCHEMA,
|
|
21
|
+
'no-inline-type-literals': rule_configs_2.NoInlineTypeLiteralsConfig.SCHEMA,
|
|
22
|
+
'no-any-unknown': rule_configs_2.NoAnyUnknownConfig.SCHEMA,
|
|
23
|
+
'no-implicit-any': rule_configs_2.NoImplicitAnyConfig.SCHEMA,
|
|
24
|
+
'prisma-validate-dtos': rule_configs_2.PrismaValidateDtosConfig.SCHEMA,
|
|
25
|
+
'prisma-converter': rule_configs_2.PrismaConverterConfig.SCHEMA,
|
|
26
|
+
'no-destructure': rule_configs_2.NoDestructureConfig.SCHEMA,
|
|
27
|
+
'no-unmanaged-exceptions': rule_configs_2.NoUnmanagedExceptionsConfig.SCHEMA,
|
|
28
|
+
'catch-error-pattern': rule_configs_2.CatchErrorPatternConfig.SCHEMA,
|
|
29
|
+
'throw-cause-required': rule_configs_2.ThrowCauseRequiredConfig.SCHEMA,
|
|
30
|
+
'angular-no-direct-api-in-resolver': rule_configs_2.AngularNoDirectApiInResolverConfig.SCHEMA,
|
|
31
|
+
'no-symbol-di-tokens': rule_configs_2.NoSymbolDiTokensConfig.SCHEMA,
|
|
32
|
+
'branch-creation-guard': rule_configs_2.BranchCreationGuardConfig.SCHEMA,
|
|
33
|
+
'pr-creation-guard': rule_configs_2.PrCreationGuardConfig.SCHEMA,
|
|
34
|
+
'merge-in-progress-guard': rule_configs_2.MergeInProgressGuardConfig.SCHEMA,
|
|
35
|
+
'pr-merge-guard': rule_configs_2.PrMergeGuardConfig.SCHEMA,
|
|
36
|
+
'redirect-how-to-merge-main': rule_configs_2.RedirectHowToMergeMainConfig.SCHEMA,
|
|
37
|
+
'feature-branch-guard': rule_configs_2.FeatureBranchGuardConfig.SCHEMA,
|
|
38
|
+
'no-file-import-cycles': rule_configs_2.NoFileImportCyclesConfig.SCHEMA,
|
|
39
|
+
'runtime-architecture': rule_configs_2.RuntimeArchitectureConfig.SCHEMA,
|
|
40
|
+
'nx-wiring': rule_configs_2.NxWiringConfig.SCHEMA,
|
|
41
|
+
'no-js-files': rule_configs_2.NoJsFilesConfig.SCHEMA,
|
|
42
|
+
'validate-ts-in-src': rule_configs_2.ValidateTsInSrcConfig.SCHEMA,
|
|
39
43
|
};
|
|
40
44
|
// Every built-in rule name that has a typed schema (code rules + bash guards). The installer uses
|
|
41
45
|
// this (with sectionForRule) to seed a fresh webpieces.config.json with every rule in its section.
|
|
@@ -248,6 +252,101 @@ function validateExcludePaths(section) {
|
|
|
248
252
|
...validateExcludeList(s['guards'], 'guards'),
|
|
249
253
|
];
|
|
250
254
|
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// match-rules — a new top-level ARRAY section (parallel to pr-gate/excludePaths). Each entry is a
|
|
257
|
+
// client-authored content guard (raw-regex patterns + message + scoping). Validated structurally here
|
|
258
|
+
// the same way pr-gate's `gates` are, because an array of objects can't be expressed in FieldDef schema.
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
function matchRulesExample() {
|
|
261
|
+
return `"match-rules": ${JSON.stringify(match_rules_config_1.DEFAULT_MATCH_RULES, null, 4)}`;
|
|
262
|
+
}
|
|
263
|
+
// webpieces-disable no-any-unknown -- generic type guard over an opaque JSON value
|
|
264
|
+
function isStringArray(value) {
|
|
265
|
+
return Array.isArray(value) && value.every(v => typeof v === 'string');
|
|
266
|
+
}
|
|
267
|
+
// Compile a pattern to validate it; returns the error message, or undefined when it compiles.
|
|
268
|
+
function regexError(pattern) {
|
|
269
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
270
|
+
try {
|
|
271
|
+
// Constructed only to validate the syntax; the object is intentionally discarded.
|
|
272
|
+
void new RegExp(pattern);
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
const error = (0, to_error_1.toError)(err);
|
|
277
|
+
return error.message;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// One entry of the match-rules array, validated field-by-field (see validateGate for the pattern).
|
|
281
|
+
// webpieces-disable no-any-unknown -- one match-rule entry from opaque consumer JSON, validated field-by-field
|
|
282
|
+
function validateMatchRule(entry, index) {
|
|
283
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
284
|
+
return [`[match-rules] entry[${index}] must be an object { name, patterns, mainMessage, mode, ignoreModifiedUntilEpoch, ... }.`];
|
|
285
|
+
}
|
|
286
|
+
// webpieces-disable no-any-unknown -- narrowing one opaque match-rule entry from consumer JSON
|
|
287
|
+
const e = entry;
|
|
288
|
+
const label = typeof e['name'] === 'string' ? `"${e['name']}"` : `entry[${index}]`;
|
|
289
|
+
const errors = [];
|
|
290
|
+
if (typeof e['name'] !== 'string' || e['name'].trim() === '')
|
|
291
|
+
errors.push(`[match-rules] entry[${index}].name must be a non-empty string (it is the disable token and report label).`);
|
|
292
|
+
if (!isStringArray(e['patterns']) || e['patterns'].length === 0) {
|
|
293
|
+
errors.push(`[match-rules] ${label}.patterns must be a non-empty string[] of regexes.`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
e['patterns'].forEach((p, pi) => {
|
|
297
|
+
const rxErr = regexError(p);
|
|
298
|
+
if (rxErr)
|
|
299
|
+
errors.push(`[match-rules] ${label}.patterns[${pi}] is not a valid regex: ${rxErr}`);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (typeof e['mainMessage'] !== 'string' || e['mainMessage'].trim() === '')
|
|
303
|
+
errors.push(`[match-rules] ${label}.mainMessage must be a non-empty string.`);
|
|
304
|
+
if (typeof e['mode'] !== 'string' || !rule_configs_1.MODIFIED_CODE_MODES.includes(e['mode']))
|
|
305
|
+
errors.push(`[match-rules] ${label}.mode must be one of: ${rule_configs_1.MODIFIED_CODE_MODES.join(', ')}.`);
|
|
306
|
+
if (typeof e['ignoreModifiedUntilEpoch'] !== 'number')
|
|
307
|
+
errors.push(`[match-rules] ${label}.ignoreModifiedUntilEpoch must be a number (0 = active; future unix-epoch seconds = temporarily off).`);
|
|
308
|
+
if (e['options'] !== undefined && !isStringArray(e['options']))
|
|
309
|
+
errors.push(`[match-rules] ${label}.options must be a string[] (omit if not needed).`);
|
|
310
|
+
if (e['allowedPaths'] !== undefined && !isStringArray(e['allowedPaths']))
|
|
311
|
+
errors.push(`[match-rules] ${label}.allowedPaths must be a string[] of globs (omit if not needed).`);
|
|
312
|
+
if (e['disableAllowed'] !== undefined && typeof e['disableAllowed'] !== 'boolean')
|
|
313
|
+
errors.push(`[match-rules] ${label}.disableAllowed must be a boolean.`);
|
|
314
|
+
if (e['ignoreRuleWhileOnBranch'] !== undefined && typeof e['ignoreRuleWhileOnBranch'] !== 'string')
|
|
315
|
+
errors.push(`[match-rules] ${label}.ignoreRuleWhileOnBranch must be a string.`);
|
|
316
|
+
return errors;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Validate the REQUIRED top-level `match-rules` array (client-authored content guards). MISSING →
|
|
320
|
+
* one error printing the ready-to-paste `no-fetch` example (add at least this; more can follow).
|
|
321
|
+
* Present-but-`[]` → allowed (a conscious opt-out, matching pr-gate mode:OFF / excludePaths []).
|
|
322
|
+
* Otherwise every entry is validated field-by-field (each regex compile-checked) plus name
|
|
323
|
+
* uniqueness. Copy-paste-friendly errors; never throws — same contract as validatePrGateSection.
|
|
324
|
+
*/
|
|
325
|
+
// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below
|
|
326
|
+
function validateMatchRulesSection(section) {
|
|
327
|
+
if (section === undefined || section === null) {
|
|
328
|
+
return [
|
|
329
|
+
`[match-rules] Not configured in webpieces.config.json. Add this REQUIRED top-level array — ` +
|
|
330
|
+
`seed it with the no-fetch guard below (you can add more entries: no-moment, no-lodash-chain, …):\n\n${matchRulesExample()}`,
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
if (!Array.isArray(section)) {
|
|
334
|
+
return [`[match-rules] Must be an array of content-guard objects. Example:\n\n${matchRulesExample()}`];
|
|
335
|
+
}
|
|
336
|
+
const errors = [];
|
|
337
|
+
const seen = new Set();
|
|
338
|
+
for (let i = 0; i < section.length; i += 1) {
|
|
339
|
+
errors.push(...validateMatchRule(section[i], i));
|
|
340
|
+
// webpieces-disable no-any-unknown -- reading the name off an opaque entry only to dedupe
|
|
341
|
+
const name = section[i]?.['name'];
|
|
342
|
+
if (typeof name === 'string') {
|
|
343
|
+
if (seen.has(name))
|
|
344
|
+
errors.push(`[match-rules] duplicate entry name "${name}" — each match-rule name must be unique.`);
|
|
345
|
+
seen.add(name);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return errors;
|
|
349
|
+
}
|
|
251
350
|
/**
|
|
252
351
|
* Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards
|
|
253
352
|
* under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef } from './field-def';\nimport { sectionForRule, isHookGuard } from './sections';\nimport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n FeatureBranchGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n} from './rule-configs';\n\n// Thin lookup table — each entry delegates to the class's own SCHEMA.\n// No field lists here; all schemas live with their config class.\nconst RULE_SCHEMAS: Record<string, Record<string, FieldDef>> = {\n 'max-method-lines': MaxMethodLinesConfig.SCHEMA,\n 'max-file-lines': MaxFileLinesConfig.SCHEMA,\n 'require-return-type': RequireReturnTypeConfig.SCHEMA,\n 'no-inline-type-literals': NoInlineTypeLiteralsConfig.SCHEMA,\n 'no-any-unknown': NoAnyUnknownConfig.SCHEMA,\n 'no-implicit-any': NoImplicitAnyConfig.SCHEMA,\n 'prisma-validate-dtos': PrismaValidateDtosConfig.SCHEMA,\n 'prisma-converter': PrismaConverterConfig.SCHEMA,\n 'no-destructure': NoDestructureConfig.SCHEMA,\n 'no-unmanaged-exceptions': NoUnmanagedExceptionsConfig.SCHEMA,\n 'catch-error-pattern': CatchErrorPatternConfig.SCHEMA,\n 'throw-cause-required': ThrowCauseRequiredConfig.SCHEMA,\n 'angular-no-direct-api-in-resolver': AngularNoDirectApiInResolverConfig.SCHEMA,\n 'no-symbol-di-tokens': NoSymbolDiTokensConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-creation-guard': PrCreationGuardConfig.SCHEMA,\n 'merge-in-progress-guard': MergeInProgressGuardConfig.SCHEMA,\n 'pr-merge-guard': PrMergeGuardConfig.SCHEMA,\n 'redirect-how-to-merge-main': RedirectHowToMergeMainConfig.SCHEMA,\n 'feature-branch-guard': FeatureBranchGuardConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'nx-wiring': NxWiringConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n};\n\n// Every built-in rule name that has a typed schema (code rules + bash guards). The installer uses\n// this (with sectionForRule) to seed a fresh webpieces.config.json with every rule in its section.\nexport function allRuleNames(): readonly string[] {\n return Object.keys(RULE_SCHEMAS);\n}\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // ignoreModifiedUntilEpoch is required on every rule; 0 keeps the rule active (epoch in the\n // past), a future unix epoch (seconds) temporarily disables it. Spell that out for the AI.\n if (key === 'ignoreModifiedUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Only required fields go in the copy-paste entry. Optional fields (e.g. the\n // universal escape hatches ignoreRuleWhileOnBranch / ignoreModifiedUntilEpoch)\n // are listed separately so the snippet doesn't over-state what's mandatory.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n const section = sectionForRule(ruleName);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"${section}\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n return out;\n}\n\n// A config key under rules/hookGuards that is not a known built-in rule (and no rulesDir is set to\n// supply custom rules). Almost always a removed/renamed rule left behind, or a typo. Actionable.\nfunction unknownRuleError(ruleName: string): string {\n return (\n `[${ruleName}] Unknown rule — not a built-in rule and no \"rulesDir\" is configured to supply ` +\n `custom rules. Remove the \"${ruleName}\" key from webpieces.config.json (it is likely a removed ` +\n `or renamed rule, or a typo).`\n );\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n hasCustomRulesDir: boolean = false,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) {\n // No built-in schema. With no rulesDir there are no custom rules, so this key is a\n // dead/typo'd entry — tell the AI to remove it (a removed rule like no-shell-substitution\n // lingers here otherwise). With a rulesDir it may be a legitimate custom rule → skip.\n if (!hasCustomRulesDir) errors.push(unknownRuleError(ruleName));\n continue;\n }\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}]`);\n continue;\n }\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n }\n }\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `ignoreModifiedUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"warningColor\": \"yellow\" }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, warningColor, disabled? }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['warningColor'] === undefined)\n errors.push(`[pr-gate] gates[${index}].warningColor is required — set it to \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n else if (g['warningColor'] !== 'yellow' && g['warningColor'] !== 'red')\n errors.push(`[pr-gate] gates[${index}].warningColor must be \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')\n errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates` array can't be expressed there, so it gets its own structural validation here.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validatePrGateSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this block under the \"commands\" ` +\n `section (set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand is required whenever the gate is active (mode !== OFF).\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)\".`,\n );\n }\n }\n\n if ('gates' in s) {\n const gates = s['gates'];\n if (!Array.isArray(gates)) {\n errors.push(`[pr-gate] \"gates\" must be an array of { name, patterns, warningColor, disabled? }.`);\n } else {\n for (let i = 0; i < gates.length; i += 1) {\n errors.push(...validateGate(gates[i], i));\n }\n }\n }\n\n return errors;\n}\n\nfunction excludePathsExample(): string {\n return '\"excludePaths\": {\\n' +\n ' \"rules\": [\"repositories/**\"],\\n' +\n ' \"guards\": []\\n' +\n ' }';\n}\n\n// One of the two excludePaths lists: required, must be a string[] (may be empty).\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed here\nfunction validateExcludeList(value: unknown, key: string): string[] {\n if (!(Array.isArray(value) && value.every(p => typeof p === 'string'))) {\n return [`[excludePaths] \"${key}\" must be a string[] of glob paths (use [] for none).`];\n }\n return [];\n}\n\n/**\n * Validate the REQUIRED top-level `excludePaths` block: two independent glob lists (`rules`,\n * `guards`) that suppress hook enforcement per file path. Required so every client upgrading is\n * forced to declare it (as [] to keep today's behavior, or with real paths). Returns copy-paste\n * friendly errors and never throws — same contract as validatePrGateSection.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateExcludePaths(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[excludePaths] Not configured in webpieces.config.json. Add this REQUIRED block ` +\n `(use empty arrays to keep enforcing everywhere):\\n\\n ${excludePathsExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[excludePaths] Must be an object with \"rules\" and \"guards\" string arrays. Example:\\n\\n ${excludePathsExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque excludePaths section from consumer JSON\n const s = section as Record<string, unknown>;\n return [\n ...validateExcludeList(s['rules'], 'rules'),\n ...validateExcludeList(s['guards'], 'guards'),\n ];\n}\n\n/**\n * Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards\n * under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a\n * \"move it\" message so the split stays clean. Unknown/custom names are ignored (they may be custom\n * rules from rulesDir). Presence (\"every built-in must be configured\") is checked separately by\n * validateWebpiecesConfig against the merged map.\n */\n// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON\nexport function validateSectionPlacement(\n rulesSection: Record<string, Record<string, unknown>>,\n hookGuardsSection: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n for (const name of Object.keys(rulesSection)) {\n if (isHookGuard(name)) {\n errors.push(\n `[${name}] is a hook guard and belongs in the \"hookGuards\" section, not \"rules\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n for (const name of Object.keys(hookGuardsSection)) {\n // Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.\n if (!isHookGuard(name) && RULE_SCHEMAS[name]) {\n errors.push(\n `[${name}] is a code rule and belongs in the \"rules\" section, not \"hookGuards\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n return errors;\n}\n\n/**\n * Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection) plus the\n * optional command-string fields. Also surfaces a migration error if a DEPRECATED top-level `pr-gate`\n * block is still present, telling the consumer to move it under `commands`.\n */\n// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON\nexport function validateCommandsSection(commands: unknown, legacyPrGate: unknown): string[] {\n const errors: string[] = [];\n\n if (legacyPrGate !== undefined) {\n errors.push(\n `[pr-gate] The top-level \"pr-gate\" block is deprecated. Move it under the \"commands\" ` +\n `section as commands[\"pr-gate\"] (run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n\n if (commands !== undefined && (typeof commands !== 'object' || commands === null || Array.isArray(commands))) {\n errors.push(`[commands] Must be an object { \"pr-gate\": {...}, \"upsertPr\": \"...\", \"mergeComplete\": \"...\" }.`);\n return errors;\n }\n\n // webpieces-disable no-any-unknown -- narrowing the opaque commands section from consumer JSON\n const c = (commands ?? {}) as Record<string, unknown>;\n\n // pr-gate is required (set mode OFF to opt out). Prefer commands[\"pr-gate\"]; fall back to the\n // legacy top-level block so an un-migrated file still validates its gate config.\n errors.push(...validatePrGateSection(c['pr-gate'] ?? legacyPrGate));\n\n for (const field of ['upsertPr', 'mergeComplete']) {\n if (field in c && typeof c[field] !== 'string') {\n errors.push(`[commands] \"${field}\" must be a string (the gated command to run).`);\n }\n }\n\n return errors;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef } from './field-def';\nimport { sectionForRule, isHookGuard } from './sections';\nimport { MODIFIED_CODE_MODES } from './rule-configs';\nimport { DEFAULT_MATCH_RULES } from './match-rules-config';\nimport { toError } from './to-error';\nimport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n FeatureBranchGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n} from './rule-configs';\n\n// Thin lookup table — each entry delegates to the class's own SCHEMA.\n// No field lists here; all schemas live with their config class.\nconst RULE_SCHEMAS: Record<string, Record<string, FieldDef>> = {\n 'max-method-lines': MaxMethodLinesConfig.SCHEMA,\n 'max-file-lines': MaxFileLinesConfig.SCHEMA,\n 'require-return-type': RequireReturnTypeConfig.SCHEMA,\n 'no-inline-type-literals': NoInlineTypeLiteralsConfig.SCHEMA,\n 'no-any-unknown': NoAnyUnknownConfig.SCHEMA,\n 'no-implicit-any': NoImplicitAnyConfig.SCHEMA,\n 'prisma-validate-dtos': PrismaValidateDtosConfig.SCHEMA,\n 'prisma-converter': PrismaConverterConfig.SCHEMA,\n 'no-destructure': NoDestructureConfig.SCHEMA,\n 'no-unmanaged-exceptions': NoUnmanagedExceptionsConfig.SCHEMA,\n 'catch-error-pattern': CatchErrorPatternConfig.SCHEMA,\n 'throw-cause-required': ThrowCauseRequiredConfig.SCHEMA,\n 'angular-no-direct-api-in-resolver': AngularNoDirectApiInResolverConfig.SCHEMA,\n 'no-symbol-di-tokens': NoSymbolDiTokensConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-creation-guard': PrCreationGuardConfig.SCHEMA,\n 'merge-in-progress-guard': MergeInProgressGuardConfig.SCHEMA,\n 'pr-merge-guard': PrMergeGuardConfig.SCHEMA,\n 'redirect-how-to-merge-main': RedirectHowToMergeMainConfig.SCHEMA,\n 'feature-branch-guard': FeatureBranchGuardConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'nx-wiring': NxWiringConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n};\n\n// Every built-in rule name that has a typed schema (code rules + bash guards). The installer uses\n// this (with sectionForRule) to seed a fresh webpieces.config.json with every rule in its section.\nexport function allRuleNames(): readonly string[] {\n return Object.keys(RULE_SCHEMAS);\n}\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // ignoreModifiedUntilEpoch is required on every rule; 0 keeps the rule active (epoch in the\n // past), a future unix epoch (seconds) temporarily disables it. Spell that out for the AI.\n if (key === 'ignoreModifiedUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Only required fields go in the copy-paste entry. Optional fields (e.g. the\n // universal escape hatches ignoreRuleWhileOnBranch / ignoreModifiedUntilEpoch)\n // are listed separately so the snippet doesn't over-state what's mandatory.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n const section = sectionForRule(ruleName);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"${section}\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n return out;\n}\n\n// A config key under rules/hookGuards that is not a known built-in rule (and no rulesDir is set to\n// supply custom rules). Almost always a removed/renamed rule left behind, or a typo. Actionable.\nfunction unknownRuleError(ruleName: string): string {\n return (\n `[${ruleName}] Unknown rule — not a built-in rule and no \"rulesDir\" is configured to supply ` +\n `custom rules. Remove the \"${ruleName}\" key from webpieces.config.json (it is likely a removed ` +\n `or renamed rule, or a typo).`\n );\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n hasCustomRulesDir: boolean = false,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) {\n // No built-in schema. With no rulesDir there are no custom rules, so this key is a\n // dead/typo'd entry — tell the AI to remove it (a removed rule like no-shell-substitution\n // lingers here otherwise). With a rulesDir it may be a legitimate custom rule → skip.\n if (!hasCustomRulesDir) errors.push(unknownRuleError(ruleName));\n continue;\n }\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}]`);\n continue;\n }\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n }\n }\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `ignoreModifiedUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"warningColor\": \"yellow\" }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, warningColor, disabled? }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['warningColor'] === undefined)\n errors.push(`[pr-gate] gates[${index}].warningColor is required — set it to \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n else if (g['warningColor'] !== 'yellow' && g['warningColor'] !== 'red')\n errors.push(`[pr-gate] gates[${index}].warningColor must be \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')\n errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates` array can't be expressed there, so it gets its own structural validation here.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validatePrGateSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this block under the \"commands\" ` +\n `section (set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand is required whenever the gate is active (mode !== OFF).\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)\".`,\n );\n }\n }\n\n if ('gates' in s) {\n const gates = s['gates'];\n if (!Array.isArray(gates)) {\n errors.push(`[pr-gate] \"gates\" must be an array of { name, patterns, warningColor, disabled? }.`);\n } else {\n for (let i = 0; i < gates.length; i += 1) {\n errors.push(...validateGate(gates[i], i));\n }\n }\n }\n\n return errors;\n}\n\nfunction excludePathsExample(): string {\n return '\"excludePaths\": {\\n' +\n ' \"rules\": [\"repositories/**\"],\\n' +\n ' \"guards\": []\\n' +\n ' }';\n}\n\n// One of the two excludePaths lists: required, must be a string[] (may be empty).\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed here\nfunction validateExcludeList(value: unknown, key: string): string[] {\n if (!(Array.isArray(value) && value.every(p => typeof p === 'string'))) {\n return [`[excludePaths] \"${key}\" must be a string[] of glob paths (use [] for none).`];\n }\n return [];\n}\n\n/**\n * Validate the REQUIRED top-level `excludePaths` block: two independent glob lists (`rules`,\n * `guards`) that suppress hook enforcement per file path. Required so every client upgrading is\n * forced to declare it (as [] to keep today's behavior, or with real paths). Returns copy-paste\n * friendly errors and never throws — same contract as validatePrGateSection.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateExcludePaths(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[excludePaths] Not configured in webpieces.config.json. Add this REQUIRED block ` +\n `(use empty arrays to keep enforcing everywhere):\\n\\n ${excludePathsExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[excludePaths] Must be an object with \"rules\" and \"guards\" string arrays. Example:\\n\\n ${excludePathsExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque excludePaths section from consumer JSON\n const s = section as Record<string, unknown>;\n return [\n ...validateExcludeList(s['rules'], 'rules'),\n ...validateExcludeList(s['guards'], 'guards'),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// match-rules — a new top-level ARRAY section (parallel to pr-gate/excludePaths). Each entry is a\n// client-authored content guard (raw-regex patterns + message + scoping). Validated structurally here\n// the same way pr-gate's `gates` are, because an array of objects can't be expressed in FieldDef schema.\n// ---------------------------------------------------------------------------\n\nfunction matchRulesExample(): string {\n return `\"match-rules\": ${JSON.stringify(DEFAULT_MATCH_RULES, null, 4)}`;\n}\n\n// webpieces-disable no-any-unknown -- generic type guard over an opaque JSON value\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every(v => typeof v === 'string');\n}\n\n// Compile a pattern to validate it; returns the error message, or undefined when it compiles.\nfunction regexError(pattern: string): string | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Constructed only to validate the syntax; the object is intentionally discarded.\n void new RegExp(pattern);\n return undefined;\n } catch (err: unknown) {\n const error = toError(err);\n return error.message;\n }\n}\n\n// One entry of the match-rules array, validated field-by-field (see validateGate for the pattern).\n// webpieces-disable no-any-unknown -- one match-rule entry from opaque consumer JSON, validated field-by-field\nfunction validateMatchRule(entry: unknown, index: number): string[] {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return [`[match-rules] entry[${index}] must be an object { name, patterns, mainMessage, mode, ignoreModifiedUntilEpoch, ... }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque match-rule entry from consumer JSON\n const e = entry as Record<string, unknown>;\n const label = typeof e['name'] === 'string' ? `\"${e['name']}\"` : `entry[${index}]`;\n const errors: string[] = [];\n\n if (typeof e['name'] !== 'string' || e['name'].trim() === '')\n errors.push(`[match-rules] entry[${index}].name must be a non-empty string (it is the disable token and report label).`);\n\n if (!isStringArray(e['patterns']) || e['patterns'].length === 0) {\n errors.push(`[match-rules] ${label}.patterns must be a non-empty string[] of regexes.`);\n } else {\n e['patterns'].forEach((p: string, pi: number) => {\n const rxErr = regexError(p);\n if (rxErr) errors.push(`[match-rules] ${label}.patterns[${pi}] is not a valid regex: ${rxErr}`);\n });\n }\n\n if (typeof e['mainMessage'] !== 'string' || e['mainMessage'].trim() === '')\n errors.push(`[match-rules] ${label}.mainMessage must be a non-empty string.`);\n\n if (typeof e['mode'] !== 'string' || !MODIFIED_CODE_MODES.includes(e['mode'] as typeof MODIFIED_CODE_MODES[number]))\n errors.push(`[match-rules] ${label}.mode must be one of: ${MODIFIED_CODE_MODES.join(', ')}.`);\n\n if (typeof e['ignoreModifiedUntilEpoch'] !== 'number')\n errors.push(`[match-rules] ${label}.ignoreModifiedUntilEpoch must be a number (0 = active; future unix-epoch seconds = temporarily off).`);\n\n if (e['options'] !== undefined && !isStringArray(e['options']))\n errors.push(`[match-rules] ${label}.options must be a string[] (omit if not needed).`);\n if (e['allowedPaths'] !== undefined && !isStringArray(e['allowedPaths']))\n errors.push(`[match-rules] ${label}.allowedPaths must be a string[] of globs (omit if not needed).`);\n if (e['disableAllowed'] !== undefined && typeof e['disableAllowed'] !== 'boolean')\n errors.push(`[match-rules] ${label}.disableAllowed must be a boolean.`);\n if (e['ignoreRuleWhileOnBranch'] !== undefined && typeof e['ignoreRuleWhileOnBranch'] !== 'string')\n errors.push(`[match-rules] ${label}.ignoreRuleWhileOnBranch must be a string.`);\n\n return errors;\n}\n\n/**\n * Validate the REQUIRED top-level `match-rules` array (client-authored content guards). MISSING →\n * one error printing the ready-to-paste `no-fetch` example (add at least this; more can follow).\n * Present-but-`[]` → allowed (a conscious opt-out, matching pr-gate mode:OFF / excludePaths []).\n * Otherwise every entry is validated field-by-field (each regex compile-checked) plus name\n * uniqueness. Copy-paste-friendly errors; never throws — same contract as validatePrGateSection.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateMatchRulesSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[match-rules] Not configured in webpieces.config.json. Add this REQUIRED top-level array — ` +\n `seed it with the no-fetch guard below (you can add more entries: no-moment, no-lodash-chain, …):\\n\\n${matchRulesExample()}`,\n ];\n }\n if (!Array.isArray(section)) {\n return [`[match-rules] Must be an array of content-guard objects. Example:\\n\\n${matchRulesExample()}`];\n }\n\n const errors: string[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < section.length; i += 1) {\n errors.push(...validateMatchRule(section[i], i));\n // webpieces-disable no-any-unknown -- reading the name off an opaque entry only to dedupe\n const name = (section[i] as Record<string, unknown> | null)?.['name'];\n if (typeof name === 'string') {\n if (seen.has(name)) errors.push(`[match-rules] duplicate entry name \"${name}\" — each match-rule name must be unique.`);\n seen.add(name);\n }\n }\n return errors;\n}\n\n/**\n * Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards\n * under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a\n * \"move it\" message so the split stays clean. Unknown/custom names are ignored (they may be custom\n * rules from rulesDir). Presence (\"every built-in must be configured\") is checked separately by\n * validateWebpiecesConfig against the merged map.\n */\n// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON\nexport function validateSectionPlacement(\n rulesSection: Record<string, Record<string, unknown>>,\n hookGuardsSection: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n for (const name of Object.keys(rulesSection)) {\n if (isHookGuard(name)) {\n errors.push(\n `[${name}] is a hook guard and belongs in the \"hookGuards\" section, not \"rules\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n for (const name of Object.keys(hookGuardsSection)) {\n // Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.\n if (!isHookGuard(name) && RULE_SCHEMAS[name]) {\n errors.push(\n `[${name}] is a code rule and belongs in the \"rules\" section, not \"hookGuards\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n return errors;\n}\n\n/**\n * Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection) plus the\n * optional command-string fields. Also surfaces a migration error if a DEPRECATED top-level `pr-gate`\n * block is still present, telling the consumer to move it under `commands`.\n */\n// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON\nexport function validateCommandsSection(commands: unknown, legacyPrGate: unknown): string[] {\n const errors: string[] = [];\n\n if (legacyPrGate !== undefined) {\n errors.push(\n `[pr-gate] The top-level \"pr-gate\" block is deprecated. Move it under the \"commands\" ` +\n `section as commands[\"pr-gate\"] (run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n\n if (commands !== undefined && (typeof commands !== 'object' || commands === null || Array.isArray(commands))) {\n errors.push(`[commands] Must be an object { \"pr-gate\": {...}, \"upsertPr\": \"...\", \"mergeComplete\": \"...\" }.`);\n return errors;\n }\n\n // webpieces-disable no-any-unknown -- narrowing the opaque commands section from consumer JSON\n const c = (commands ?? {}) as Record<string, unknown>;\n\n // pr-gate is required (set mode OFF to opt out). Prefer commands[\"pr-gate\"]; fall back to the\n // legacy top-level block so an un-migrated file still validates its gate config.\n errors.push(...validatePrGateSection(c['pr-gate'] ?? legacyPrGate));\n\n for (const field of ['upsertPr', 'mergeComplete']) {\n if (field in c && typeof c[field] !== 'string') {\n errors.push(`[commands] \"${field}\" must be a string (the gated command to run).`);\n }\n }\n\n return errors;\n}\n"]}
|