@webpieces/code-rules 0.3.349 → 0.3.351
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/code-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.351",
|
|
4
4
|
"description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/tooling/code-rules"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/rules-config": "0.3.
|
|
23
|
+
"@webpieces/rules-config": "0.3.351"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
package/src/cli.js
CHANGED
|
@@ -7,7 +7,9 @@ const validate_code_1 = tslib_1.__importDefault(require("./validate-code"));
|
|
|
7
7
|
async function main() {
|
|
8
8
|
// webpieces-disable no-unmanaged-exceptions -- global entry point for code-rules CLI
|
|
9
9
|
try {
|
|
10
|
-
|
|
10
|
+
// Anchor at the repo root so `.webpieces/instruct-ai` docs are written there, not in whatever
|
|
11
|
+
// subdir this CLI happened to be invoked from.
|
|
12
|
+
const workspaceRoot = new rules_config_1.RepoRootFinder().resolveRepoRoot(process.cwd());
|
|
11
13
|
const result = await (0, validate_code_1.default)(workspaceRoot);
|
|
12
14
|
process.exit(result.success ? 0 : 1);
|
|
13
15
|
}
|
package/src/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/cli.ts"],"names":[],"mappings":";;;;AACA,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/cli.ts"],"names":[],"mappings":";;;;AACA,0DAAgG;AAChG,4EAA8C;AAE9C,KAAK,UAAU,IAAI;IACf,qFAAqF;IACrF,IAAI,CAAC;QACD,8FAA8F;QAC9F,+CAA+C;QAC/C,MAAM,aAAa,GAAG,IAAI,6BAAc,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAe,EAAC,aAAa,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,sBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,KAAK,YAAY,4BAAa,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtC,CAAC;aAAM,IAAI,GAAG,YAAY,4BAAa,EAAE,CAAC;YACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,kCAAkC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { InformAiError, RuleFailError, toError, RepoRootFinder } from '@webpieces/rules-config';\nimport runValidateCode from './validate-code';\n\nasync function main(): Promise<void> {\n // webpieces-disable no-unmanaged-exceptions -- global entry point for code-rules CLI\n try {\n // Anchor at the repo root so `.webpieces/instruct-ai` docs are written there, not in whatever\n // subdir this CLI happened to be invoked from.\n const workspaceRoot = new RepoRootFinder().resolveRepoRoot(process.cwd());\n const result = await runValidateCode(workspaceRoot);\n process.exit(result.success ? 0 : 1);\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof RuleFailError) {\n console.error(error.humanMessage);\n } else if (err instanceof InformAiError) {\n console.error(error.message);\n } else {\n console.error(`[code-rules] unexpected error: ${error.message}`);\n }\n process.exit(1);\n }\n}\n\nmain();\n"]}
|
|
@@ -43,7 +43,6 @@ Let the ONE top-level main()/runMain try-catch pick the exit code (non-zero on f
|
|
|
43
43
|
if (require.main === module) runMain(main)
|
|
44
44
|
Need a specific exit code deep down? throw new CliExitError(code, message) — runMain maps it.
|
|
45
45
|
Also: do NOT import another module's main — call a named function; only a thin wrapper calls main.
|
|
46
|
-
READ .webpieces/instruct-ai/webpieces.noexitinmain.md for the full pattern.
|
|
47
46
|
Last resort: append // webpieces-disable no-process-exit-outside-main -- <reason> at a genuine terminal boundary.`;
|
|
48
47
|
function isTestFile(filePath) {
|
|
49
48
|
return filePath.includes('.spec.ts') || filePath.includes('.test.ts') || filePath.includes('__tests__/');
|
|
@@ -126,6 +125,7 @@ function reportViolations(workspaceRoot, violations, mode, disableAllowed) {
|
|
|
126
125
|
console.error('❌ process.exit() outside main()/runMain (or an import of another module\'s main)!');
|
|
127
126
|
console.error('');
|
|
128
127
|
console.error(SHARED_MESSAGE);
|
|
128
|
+
console.error(`READ ${new rules_config_1.RepoRootFinder().instructAiDocPath(workspaceRoot, INSTRUCT_FILE)} for the full pattern.`);
|
|
129
129
|
console.error('');
|
|
130
130
|
for (const v of violations) {
|
|
131
131
|
console.error(` ❌ ${v.file}:${v.line}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-no-process-exit-outside-main.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-process-exit-outside-main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Process Exit Outside Main\n *\n * Flags `process.exit(...)` outside a `main()`/`runMain` wrapper, and `import { main }` from another\n * module. A `process.exit` deep in a reused call stack crashes a server or command far too early and\n * unexpectedly — and with exit 0 it masquerades as success. Throw a semantic error instead and let\n * the ONE top-level `main()`/`runMain` try-catch pick the exit code.\n *\n * See .webpieces/instruct-ai/webpieces.noexitinmain.md (generated on violation) for the full pattern.\n *\n * ALLOWED\n * - A `process.exit` whose nearest enclosing function is named `main` or `runMain`.\n * - Test files (*.test.ts, *.spec.ts, __tests__/**).\n * - Lines with `// webpieces-disable no-process-exit-outside-main -- <reason>` (when disableAllowed).\n *\n * MODES (LINE-BASED)\n * - OFF: Skip.\n * - NEW_AND_MODIFIED_CODE: Flag only on changed lines (diff hunks).\n * - NEW_AND_MODIFIED_FILES: Flag every occurrence in any modified file.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { hasDisable, RULE_NAMES, NoProcessExitOutsideMainConfig, ModifiedCodeMode, detectBase, getChangedFiles, getFileDiff, getChangedLineNumbers, writeTemplateIfMissing } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst INSTRUCT_FILE = 'webpieces.noexitinmain.md';\nconst EXIT_REGEX = /\\bprocess\\.exit\\s*\\(/;\nconst IMPORT_MAIN_REGEX = /^\\s*import\\b[^;]*\\{[^}]*\\bmain\\b[^}]*\\}[^;]*\\bfrom\\b/;\n// Named function / arrow-const DEFINITION openers (never a call like `main()`), used to find the\n// enclosing function's name via a backward scan.\nconst FUNC_DEF_REGEX = /(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(|(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?(?:\\(|function\\b)/;\nconst ALLOWED_FN = new Set(['main', 'runMain']);\n\nconst SHARED_MESSAGE = `Do not call process.exit() deep in the stack — any code may be reused, and a deep exit crashes a\nserver or command far too early (exit 0 even masquerades as success). Throw a semantic error instead:\n - Expected/normal failure → your domain's failure error (in the rules engine: RuleFailError).\n - A bug / broken precondition (should-never-happen, missing required var) → an ordinary Error.\nLet the ONE top-level main()/runMain try-catch pick the exit code (non-zero on failure, 0 on success):\n if (require.main === module) runMain(main)\nNeed a specific exit code deep down? throw new CliExitError(code, message) — runMain maps it.\nAlso: do NOT import another module's main — call a named function; only a thin wrapper calls main.\nREAD .webpieces/instruct-ai/webpieces.noexitinmain.md for the full pattern.\nLast resort: append // webpieces-disable no-process-exit-outside-main -- <reason> at a genuine terminal boundary.`;\n\ninterface ExitViolation {\n file: string;\n line: number;\n context: string;\n}\n\ninterface ExitViolationInfo {\n line: number;\n context: string;\n hasDisableComment: boolean;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') || filePath.includes('.test.ts') || filePath.includes('__tests__/');\n}\n\nfunction stripLineComments(line: string): string {\n const idx = line.indexOf('//');\n if (idx === -1) return line;\n return line.substring(0, idx);\n}\n\n// Walk backward from `idx` for the nearest NAMED function/arrow definition and return its name.\n// Line-based heuristic (no AST): recognises a `main`/`runMain` wrapper enclosing the exit.\nfunction enclosingFunctionName(strippedLines: string[], idx: number): string | null {\n for (let j = idx; j >= 0; j -= 1) {\n const m = FUNC_DEF_REGEX.exec(strippedLines[j] ?? '');\n if (m) return m[1] ?? m[2] ?? null;\n }\n return null;\n}\n\nfunction resolveDisable(disabled: boolean, disableAllowed: boolean): boolean {\n if (!disableAllowed && disabled) return false;\n return disabled;\n}\n\nexport function findExitViolationsInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): ExitViolationInfo[] {\n if (isTestFile(filePath)) return [];\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n const stripped = lines.map((l: string): string => stripLineComments(l));\n const violations: ExitViolationInfo[] = [];\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i] ?? '';\n const strippedLine = stripped[i] ?? '';\n const isExit = EXIT_REGEX.test(strippedLine) && !ALLOWED_FN.has(enclosingFunctionName(stripped, i) ?? '');\n const isImportMain = IMPORT_MAIN_REGEX.test(strippedLine);\n if (!isExit && !isImportMain) continue;\n\n const lineNum = i + 1;\n const prevLine = i > 0 ? (lines[i - 1] ?? '') : '';\n const disabled = hasDisable(line, RULE_NAMES.NO_PROCESS_EXIT_OUTSIDE_MAIN) || hasDisable(prevLine, RULE_NAMES.NO_PROCESS_EXIT_OUTSIDE_MAIN);\n violations.push({ line: lineNum, context: line.trim(), hasDisableComment: resolveDisable(disabled, disableAllowed) });\n }\n return violations;\n}\n\nfunction findViolationsForModifiedCode(workspaceRoot: string, changedFiles: string[], base: string, head: string | undefined, disableAllowed: boolean): ExitViolation[] {\n const violations: ExitViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const v of findExitViolationsInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n if (!changedLines.has(v.line)) continue;\n violations.push({ file, line: v.line, context: v.context });\n }\n }\n return violations;\n}\n\nfunction findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): ExitViolation[] {\n const violations: ExitViolation[] = [];\n for (const file of changedFiles) {\n for (const v of findExitViolationsInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n violations.push({ file, line: v.line, context: v.context });\n }\n }\n return violations;\n}\n\nfunction reportViolations(workspaceRoot: string, violations: ExitViolation[], mode: ModifiedCodeMode, disableAllowed: boolean): void {\n writeTemplateIfMissing(workspaceRoot, INSTRUCT_FILE);\n console.error('');\n console.error('❌ process.exit() outside main()/runMain (or an import of another module\\'s main)!');\n console.error('');\n console.error(SHARED_MESSAGE);\n console.error('');\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` ${v.context}`);\n }\n console.error('');\n console.error(disableAllowed\n ? ' Escape hatch (genuine terminal boundary only): // webpieces-disable no-process-exit-outside-main -- <reason>'\n : ' Escape hatch: DISABLED (disableAllowed: false)');\n console.error(`\\n Current mode: ${mode}\\n`);\n}\n\nfunction resolveMode(normalMode: ModifiedCodeMode, epoch: number | undefined, branchPattern: string | undefined): ModifiedCodeMode {\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping no-process-exit-outside-main validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: NoProcessExitOutsideMainConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ModifiedCodeMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const disableAllowed = options.disableAllowed ?? true;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-process-exit-outside-main validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No Process Exit Outside Main\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping no-process-exit-outside-main validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\n\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n let violations: ExitViolation[] = [];\n if (mode === 'NEW_AND_MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'NEW_AND_MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n }\n\n if (violations.length === 0) {\n console.log('✅ No process.exit-outside-main violations found');\n return { success: true };\n }\n\n reportViolations(workspaceRoot, violations, mode, disableAllowed);\n return { success: false };\n}\n\nexport class NoProcessExitOutsideMainValidator extends CodeValidator<NoProcessExitOutsideMainConfig> {\n constructor(config: NoProcessExitOutsideMainConfig) {\n super(config, 'no-process-exit-outside-main');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-no-process-exit-outside-main.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-process-exit-outside-main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Process Exit Outside Main\n *\n * Flags `process.exit(...)` outside a `main()`/`runMain` wrapper, and `import { main }` from another\n * module. A `process.exit` deep in a reused call stack crashes a server or command far too early and\n * unexpectedly — and with exit 0 it masquerades as success. Throw a semantic error instead and let\n * the ONE top-level `main()`/`runMain` try-catch pick the exit code.\n *\n * See .webpieces/instruct-ai/webpieces.noexitinmain.md (generated on violation) for the full pattern.\n *\n * ALLOWED\n * - A `process.exit` whose nearest enclosing function is named `main` or `runMain`.\n * - Test files (*.test.ts, *.spec.ts, __tests__/**).\n * - Lines with `// webpieces-disable no-process-exit-outside-main -- <reason>` (when disableAllowed).\n *\n * MODES (LINE-BASED)\n * - OFF: Skip.\n * - NEW_AND_MODIFIED_CODE: Flag only on changed lines (diff hunks).\n * - NEW_AND_MODIFIED_FILES: Flag every occurrence in any modified file.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { hasDisable, RULE_NAMES, NoProcessExitOutsideMainConfig, ModifiedCodeMode, detectBase, getChangedFiles, getFileDiff, getChangedLineNumbers, writeTemplateIfMissing, RepoRootFinder } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst INSTRUCT_FILE = 'webpieces.noexitinmain.md';\nconst EXIT_REGEX = /\\bprocess\\.exit\\s*\\(/;\nconst IMPORT_MAIN_REGEX = /^\\s*import\\b[^;]*\\{[^}]*\\bmain\\b[^}]*\\}[^;]*\\bfrom\\b/;\n// Named function / arrow-const DEFINITION openers (never a call like `main()`), used to find the\n// enclosing function's name via a backward scan.\nconst FUNC_DEF_REGEX = /(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(|(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?(?:\\(|function\\b)/;\nconst ALLOWED_FN = new Set(['main', 'runMain']);\n\nconst SHARED_MESSAGE = `Do not call process.exit() deep in the stack — any code may be reused, and a deep exit crashes a\nserver or command far too early (exit 0 even masquerades as success). Throw a semantic error instead:\n - Expected/normal failure → your domain's failure error (in the rules engine: RuleFailError).\n - A bug / broken precondition (should-never-happen, missing required var) → an ordinary Error.\nLet the ONE top-level main()/runMain try-catch pick the exit code (non-zero on failure, 0 on success):\n if (require.main === module) runMain(main)\nNeed a specific exit code deep down? throw new CliExitError(code, message) — runMain maps it.\nAlso: do NOT import another module's main — call a named function; only a thin wrapper calls main.\nLast resort: append // webpieces-disable no-process-exit-outside-main -- <reason> at a genuine terminal boundary.`;\n\ninterface ExitViolation {\n file: string;\n line: number;\n context: string;\n}\n\ninterface ExitViolationInfo {\n line: number;\n context: string;\n hasDisableComment: boolean;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') || filePath.includes('.test.ts') || filePath.includes('__tests__/');\n}\n\nfunction stripLineComments(line: string): string {\n const idx = line.indexOf('//');\n if (idx === -1) return line;\n return line.substring(0, idx);\n}\n\n// Walk backward from `idx` for the nearest NAMED function/arrow definition and return its name.\n// Line-based heuristic (no AST): recognises a `main`/`runMain` wrapper enclosing the exit.\nfunction enclosingFunctionName(strippedLines: string[], idx: number): string | null {\n for (let j = idx; j >= 0; j -= 1) {\n const m = FUNC_DEF_REGEX.exec(strippedLines[j] ?? '');\n if (m) return m[1] ?? m[2] ?? null;\n }\n return null;\n}\n\nfunction resolveDisable(disabled: boolean, disableAllowed: boolean): boolean {\n if (!disableAllowed && disabled) return false;\n return disabled;\n}\n\nexport function findExitViolationsInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): ExitViolationInfo[] {\n if (isTestFile(filePath)) return [];\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n const stripped = lines.map((l: string): string => stripLineComments(l));\n const violations: ExitViolationInfo[] = [];\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i] ?? '';\n const strippedLine = stripped[i] ?? '';\n const isExit = EXIT_REGEX.test(strippedLine) && !ALLOWED_FN.has(enclosingFunctionName(stripped, i) ?? '');\n const isImportMain = IMPORT_MAIN_REGEX.test(strippedLine);\n if (!isExit && !isImportMain) continue;\n\n const lineNum = i + 1;\n const prevLine = i > 0 ? (lines[i - 1] ?? '') : '';\n const disabled = hasDisable(line, RULE_NAMES.NO_PROCESS_EXIT_OUTSIDE_MAIN) || hasDisable(prevLine, RULE_NAMES.NO_PROCESS_EXIT_OUTSIDE_MAIN);\n violations.push({ line: lineNum, context: line.trim(), hasDisableComment: resolveDisable(disabled, disableAllowed) });\n }\n return violations;\n}\n\nfunction findViolationsForModifiedCode(workspaceRoot: string, changedFiles: string[], base: string, head: string | undefined, disableAllowed: boolean): ExitViolation[] {\n const violations: ExitViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const v of findExitViolationsInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n if (!changedLines.has(v.line)) continue;\n violations.push({ file, line: v.line, context: v.context });\n }\n }\n return violations;\n}\n\nfunction findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): ExitViolation[] {\n const violations: ExitViolation[] = [];\n for (const file of changedFiles) {\n for (const v of findExitViolationsInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n violations.push({ file, line: v.line, context: v.context });\n }\n }\n return violations;\n}\n\nfunction reportViolations(workspaceRoot: string, violations: ExitViolation[], mode: ModifiedCodeMode, disableAllowed: boolean): void {\n writeTemplateIfMissing(workspaceRoot, INSTRUCT_FILE);\n console.error('');\n console.error('❌ process.exit() outside main()/runMain (or an import of another module\\'s main)!');\n console.error('');\n console.error(SHARED_MESSAGE);\n console.error(`READ ${new RepoRootFinder().instructAiDocPath(workspaceRoot, INSTRUCT_FILE)} for the full pattern.`);\n console.error('');\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` ${v.context}`);\n }\n console.error('');\n console.error(disableAllowed\n ? ' Escape hatch (genuine terminal boundary only): // webpieces-disable no-process-exit-outside-main -- <reason>'\n : ' Escape hatch: DISABLED (disableAllowed: false)');\n console.error(`\\n Current mode: ${mode}\\n`);\n}\n\nfunction resolveMode(normalMode: ModifiedCodeMode, epoch: number | undefined, branchPattern: string | undefined): ModifiedCodeMode {\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping no-process-exit-outside-main validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: NoProcessExitOutsideMainConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ModifiedCodeMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const disableAllowed = options.disableAllowed ?? true;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-process-exit-outside-main validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No Process Exit Outside Main\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping no-process-exit-outside-main validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\n\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n let violations: ExitViolation[] = [];\n if (mode === 'NEW_AND_MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'NEW_AND_MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n }\n\n if (violations.length === 0) {\n console.log('✅ No process.exit-outside-main violations found');\n return { success: true };\n }\n\n reportViolations(workspaceRoot, violations, mode, disableAllowed);\n return { success: false };\n}\n\nexport class NoProcessExitOutsideMainValidator extends CodeValidator<NoProcessExitOutsideMainConfig> {\n constructor(config: NoProcessExitOutsideMainConfig) {\n super(config, 'no-process-exit-outside-main');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
|
package/src/validate-role-tag.js
CHANGED
|
@@ -23,7 +23,7 @@ exports.findRoleUntaggedProjects = findRoleUntaggedProjects;
|
|
|
23
23
|
const code_validator_1 = require("./code-validator");
|
|
24
24
|
const tag_rule_1 = require("./tag-rule");
|
|
25
25
|
const ROLE_TAG_PREFIX = 'role:';
|
|
26
|
-
const DEFAULT_KNOWN_TYPES = ['server', 'designed-lib', 'lib', 'client', 'api-lib'];
|
|
26
|
+
const DEFAULT_KNOWN_TYPES = ['server', 'app', 'designed-lib', 'lib', 'client', 'api-lib'];
|
|
27
27
|
/** The role-tag flavor of the shared missing-tag scan (exported for tests). */
|
|
28
28
|
function findRoleUntaggedProjects(workspaceRoot, changedFiles) {
|
|
29
29
|
return (0, tag_rule_1.findProjectsMissingTag)(workspaceRoot, changedFiles, ROLE_TAG_PREFIX);
|
|
@@ -34,7 +34,8 @@ function reportViolations(untagged, knownTypes, mode) {
|
|
|
34
34
|
console.error('❌ Every modified project must declare its role (a role tag)!');
|
|
35
35
|
console.error('');
|
|
36
36
|
console.error(` Add ONE of these to the project's project.json "tags" array: ${suggestion}`);
|
|
37
|
-
console.error(' (server = runnable app ·
|
|
37
|
+
console.error(' (server = runnable HTTP app · app = runnable non-HTTP app via container.get(XxxApp) ·');
|
|
38
|
+
console.error(' designed-lib = library with a @DocumentDesign DI design ·');
|
|
38
39
|
console.error(' lib = plain library, no design · client = client app e.g. angular.)');
|
|
39
40
|
console.error(' This role drives DI-design generation, the arch graph, and the role-dependency rule.');
|
|
40
41
|
console.error('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-role-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-role-tag.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAUH,4DAEC;AATD,qDAAiE;AACjE,yCAAqG;AAErG,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-role-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-role-tag.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAUH,4DAEC;AATD,qDAAiE;AACjE,yCAAqG;AAErG,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAE1F,+EAA+E;AAC/E,SAAgB,wBAAwB,CAAC,aAAqB,EAAE,YAAsB;IAClF,OAAO,IAAA,iCAAsB,EAAC,aAAa,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA6B,EAAE,UAAoB,EAAE,IAAiB;IAC5F,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAC9E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,mEAAmE,UAAU,EAAE,CAAC,CAAC;IAC/F,OAAO,CAAC,KAAK,CAAC,0FAA0F,CAAC,CAAC;IAC1G,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;IAC/E,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAC;IACzF,OAAO,CAAC,KAAK,CAAC,yFAAyF,CAAC,CAAC;IACzG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAElB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,2BAA2B,eAAe,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,sBAAW,CAAC,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;AAEtH,MAAa,gBAAiB,SAAQ,8BAA4B;IAC9D,YAAY,MAAqB;QAC7B,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAqB;QAC3B,OAAO,IAAA,0BAAe,EAAC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IACtE,CAAC;CACJ;AARD,4CAQC","sourcesContent":["/**\n * Validate Role Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `role:<value>` nx tag in its project.json. That tag is the project's ROLE\n * (orthogonal to its `framework` libType):\n *\n * role:server | role:designed-lib | role:lib | role:client\n *\n * It is the source of truth for the `role` field written into\n * architecture/dependencies.json, for the `role-dependency` edge rule (apps are\n * never depended upon), and for DI-design generation (server → @DocumentDesign,\n * designed-lib → @DocumentDesign, lib → none, client → angular design).\n *\n * The project-walking + diff-scoping + mode logic is shared with the\n * `framework-tag` rule in `tag-rule.ts`; this file supplies only the\n * role-specific prefix, known values, and violation message.\n */\n\nimport { ProjectMode, RoleTagConfig } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { MissingTagProject, TagRuleSpec, findProjectsMissingTag, runTagValidator } from './tag-rule';\n\nconst ROLE_TAG_PREFIX = 'role:';\nconst DEFAULT_KNOWN_TYPES = ['server', 'app', 'designed-lib', 'lib', 'client', 'api-lib'];\n\n/** The role-tag flavor of the shared missing-tag scan (exported for tests). */\nexport function findRoleUntaggedProjects(workspaceRoot: string, changedFiles: string[]): MissingTagProject[] {\n return findProjectsMissingTag(workspaceRoot, changedFiles, ROLE_TAG_PREFIX);\n}\n\nfunction reportViolations(untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode): void {\n const suggestion = knownTypes.map((t: string) => `${ROLE_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare its role (a role tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (server = runnable HTTP app · app = runnable non-HTTP app via container.get(XxxApp) ·');\n console.error(' designed-lib = library with a @DocumentDesign DI design ·');\n console.error(' lib = plain library, no design · client = client app e.g. angular.)');\n console.error(' This role drives DI-design generation, the arch graph, and the role-dependency rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${ROLE_TAG_PREFIX}lib\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nconst ROLE_TAG_SPEC = new TagRuleSpec(ROLE_TAG_PREFIX, 'role-tag', 'Role Tag', DEFAULT_KNOWN_TYPES, reportViolations);\n\nexport class RoleTagValidator extends CodeValidator<RoleTagConfig> {\n constructor(config: RoleTagConfig) {\n super(config, 'role-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runTagValidator(this.config, workspaceRoot, ROLE_TAG_SPEC);\n }\n}\n"]}
|