@webpieces/rules-config 0.3.334 → 0.3.335

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.3.334",
3
+ "version": "0.3.335",
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",
@@ -32,7 +32,7 @@ exports.defaultRules = {
32
32
  'no-process-exit-outside-main': {},
33
33
  'inject-annotation-not-needed-for-concrete-class': {},
34
34
  'framework-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['browser', 'react', 'angular', 'node', 'express'] },
35
- 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'designed-lib', 'lib', 'client'] },
35
+ 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'designed-lib', 'lib', 'client', 'api-lib'] },
36
36
  'nx-wiring': { mode: 'RUN_EVERY_TIME' },
37
37
  'di-graph': { mode: 'RUN_EVERY_TIME' },
38
38
  'missing-design-annotation': { mode: 'RUN_EVERY_TIME' },
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/default-rules.ts"],"names":[],"mappings":";;;AAAA,2DAA2D;AAC3D,4EAA4E;AAC5E,kEAAkE;AAClE,0EAA0E;AAC1E,MAAM,qBAAqB,GAAsB;IAC7C,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACrC,WAAW,EAAE,mBAAmB;CACnC,CAAC;AAEF,2EAA2E;AAC3E,6EAA6E;AAC7E,sFAAsF;AACtF,iFAAiF;AACpE,QAAA,YAAY,GAA4C;IACjE,gBAAgB,EAAE,EAAE;IACpB,iBAAiB,EAAE,EAAE;IACrB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;IAChC,kBAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACjC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,gBAAgB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IACzC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,uBAAuB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACnD,sBAAsB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IAC5G,sBAAsB,EAAE,EAAE;IAC1B,kBAAkB,EAAE,EAAE;IACtB,mCAAmC,EAAE,EAAE;IACvC,qBAAqB,EAAE,EAAE;IACzB,8BAA8B,EAAE,EAAE;IAClC,iDAAiD,EAAE,EAAE;IACrD,eAAe,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE;IAC9G,UAAU,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE;IAClG,WAAW,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvC,UAAU,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACtC,2BAA2B,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvD,oBAAoB,EAAE;QAClB,IAAI,EAAE,wBAAwB;QAC9B,gBAAgB,EAAE,CAAC,eAAe,CAAC;QACnC,YAAY,EAAE,CAAC,GAAG,qBAAqB,CAAC;KAC3C;IACD,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;IAC9B,uBAAuB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,sCAAsC,EAAE;IAChG,2BAA2B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC3C,yBAAyB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACzC,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAChC,4BAA4B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;CAC/C,CAAC;AAEW,QAAA,eAAe,GAAsB,EAAE,CAAC","sourcesContent":["// Default holistic exclude list for the validate-ts-in-src\n// rules. Bare names match a directory segment at any depth; globs match the\n// workspace-relative path. `**/*.d.ts` (ambient declarations) and\n// `**/jest.config.ts` legitimately live outside src/ and are exempt here.\nconst DEFAULT_EXCLUDE_PATHS: readonly string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\n// On/off is driven by `mode` (\"OFF\" disables; an absent mode leaves a rule\n// on). Code-rules entries omit `mode` so each executor keeps its own default\n// scope; structural rules declare `mode: 'RUN_EVERY_TIME'`, bash guards `mode: 'ON'`.\n// webpieces-disable no-any-unknown -- rule options are opaque at framework level\nexport const defaultRules: Record<string, Record<string, unknown>> = {\n 'no-any-unknown': {},\n 'no-implicit-any': {},\n 'max-file-lines': { limit: 900 },\n 'max-method-lines': { limit: 80 },\n 'require-return-type': {},\n 'no-inline-type-literals': {},\n 'no-destructure': { allowTopLevel: true },\n 'catch-error-pattern': {},\n 'no-unmanaged-exceptions': {},\n 'no-file-import-cycles': { mode: 'RUN_EVERY_TIME' },\n 'runtime-architecture': { mode: 'RUN_EVERY_TIME', servicePaths: [], apiProjectPaths: [], allowedCycles: [] },\n 'prisma-validate-dtos': {},\n 'prisma-converter': {},\n 'angular-no-direct-api-in-resolver': {},\n 'no-symbol-di-tokens': {},\n 'no-process-exit-outside-main': {},\n 'inject-annotation-not-needed-for-concrete-class': {},\n 'framework-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['browser', 'react', 'angular', 'node', 'express'] },\n 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'designed-lib', 'lib', 'client'] },\n 'nx-wiring': { mode: 'RUN_EVERY_TIME' },\n 'di-graph': { mode: 'RUN_EVERY_TIME' },\n 'missing-design-annotation': { mode: 'RUN_EVERY_TIME' },\n 'validate-ts-in-src': {\n mode: 'NEW_AND_MODIFIED_FILES',\n allowedRootFiles: ['jest.setup.ts'],\n excludePaths: [...DEFAULT_EXCLUDE_PATHS],\n },\n 'no-js-files': { mode: 'OFF' },\n 'branch-creation-guard': { mode: 'ON', subBranchNaming: 'feature/<ticket>/<short-description>' },\n 'pr-creation-or-push-guard': { mode: 'ON' },\n 'merge-in-progress-guard': { mode: 'ON' },\n 'pr-merge-guard': { mode: 'ON' },\n 'redirect-how-to-merge-main': { mode: 'ON' },\n};\n\nexport const defaultRulesDir: readonly string[] = [];\n"]}
1
+ {"version":3,"file":"default-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/default-rules.ts"],"names":[],"mappings":";;;AAAA,2DAA2D;AAC3D,4EAA4E;AAC5E,kEAAkE;AAClE,0EAA0E;AAC1E,MAAM,qBAAqB,GAAsB;IAC7C,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACrC,WAAW,EAAE,mBAAmB;CACnC,CAAC;AAEF,2EAA2E;AAC3E,6EAA6E;AAC7E,sFAAsF;AACtF,iFAAiF;AACpE,QAAA,YAAY,GAA4C;IACjE,gBAAgB,EAAE,EAAE;IACpB,iBAAiB,EAAE,EAAE;IACrB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;IAChC,kBAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACjC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,gBAAgB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IACzC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,uBAAuB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACnD,sBAAsB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IAC5G,sBAAsB,EAAE,EAAE;IAC1B,kBAAkB,EAAE,EAAE;IACtB,mCAAmC,EAAE,EAAE;IACvC,qBAAqB,EAAE,EAAE;IACzB,8BAA8B,EAAE,EAAE;IAClC,iDAAiD,EAAE,EAAE;IACrD,eAAe,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE;IAC9G,UAAU,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE;IAC7G,WAAW,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvC,UAAU,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACtC,2BAA2B,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvD,oBAAoB,EAAE;QAClB,IAAI,EAAE,wBAAwB;QAC9B,gBAAgB,EAAE,CAAC,eAAe,CAAC;QACnC,YAAY,EAAE,CAAC,GAAG,qBAAqB,CAAC;KAC3C;IACD,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;IAC9B,uBAAuB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,sCAAsC,EAAE;IAChG,2BAA2B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC3C,yBAAyB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACzC,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAChC,4BAA4B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;CAC/C,CAAC;AAEW,QAAA,eAAe,GAAsB,EAAE,CAAC","sourcesContent":["// Default holistic exclude list for the validate-ts-in-src\n// rules. Bare names match a directory segment at any depth; globs match the\n// workspace-relative path. `**/*.d.ts` (ambient declarations) and\n// `**/jest.config.ts` legitimately live outside src/ and are exempt here.\nconst DEFAULT_EXCLUDE_PATHS: readonly string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\n// On/off is driven by `mode` (\"OFF\" disables; an absent mode leaves a rule\n// on). Code-rules entries omit `mode` so each executor keeps its own default\n// scope; structural rules declare `mode: 'RUN_EVERY_TIME'`, bash guards `mode: 'ON'`.\n// webpieces-disable no-any-unknown -- rule options are opaque at framework level\nexport const defaultRules: Record<string, Record<string, unknown>> = {\n 'no-any-unknown': {},\n 'no-implicit-any': {},\n 'max-file-lines': { limit: 900 },\n 'max-method-lines': { limit: 80 },\n 'require-return-type': {},\n 'no-inline-type-literals': {},\n 'no-destructure': { allowTopLevel: true },\n 'catch-error-pattern': {},\n 'no-unmanaged-exceptions': {},\n 'no-file-import-cycles': { mode: 'RUN_EVERY_TIME' },\n 'runtime-architecture': { mode: 'RUN_EVERY_TIME', servicePaths: [], apiProjectPaths: [], allowedCycles: [] },\n 'prisma-validate-dtos': {},\n 'prisma-converter': {},\n 'angular-no-direct-api-in-resolver': {},\n 'no-symbol-di-tokens': {},\n 'no-process-exit-outside-main': {},\n 'inject-annotation-not-needed-for-concrete-class': {},\n 'framework-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['browser', 'react', 'angular', 'node', 'express'] },\n 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'designed-lib', 'lib', 'client', 'api-lib'] },\n 'nx-wiring': { mode: 'RUN_EVERY_TIME' },\n 'di-graph': { mode: 'RUN_EVERY_TIME' },\n 'missing-design-annotation': { mode: 'RUN_EVERY_TIME' },\n 'validate-ts-in-src': {\n mode: 'NEW_AND_MODIFIED_FILES',\n allowedRootFiles: ['jest.setup.ts'],\n excludePaths: [...DEFAULT_EXCLUDE_PATHS],\n },\n 'no-js-files': { mode: 'OFF' },\n 'branch-creation-guard': { mode: 'ON', subBranchNaming: 'feature/<ticket>/<short-description>' },\n 'pr-creation-or-push-guard': { mode: 'ON' },\n 'merge-in-progress-guard': { mode: 'ON' },\n 'pr-merge-guard': { mode: 'ON' },\n 'redirect-how-to-merge-main': { mode: 'ON' },\n};\n\nexport const defaultRulesDir: readonly string[] = [];\n"]}
@@ -165,7 +165,7 @@ exports.DEFAULT_MATCH_RULES = [
165
165
  '\\bnew\\s+Request\\s*\\(',
166
166
  "from\\s+['\"](node-fetch|got|undici)['\"]",
167
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 { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);",
168
+ "PREFERRED: generate a client from the API you want to call — import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createRpcClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);",
169
169
  'Reuse an existing API contract already defined in your repo (under libraries/apis/**) and generate its client the same way.',
170
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
171
  ], true, ['packages/http/http-client/**', 'libraries/apis-external/**', 'libraries/apis/external/**']),
@@ -1 +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 { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createClient(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"]}
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 { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createRpcClient(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"]}