@webpieces/nx-webpieces-rules 0.4.946 → 0.4.948
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/README.md +20 -11
- package/package.json +7 -7
- package/src/executors/validate-no-file-import-cycles/executor.js +7 -4
- package/src/executors/validate-no-file-import-cycles/executor.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +18 -12
- package/src/lib/graph-color-modes.js +18 -20
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-legend.d.ts +11 -3
- package/src/lib/graph-legend.js +25 -9
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +2 -1
- package/src/lib/graph-mode-state.client.js +3 -1
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +2 -2
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.js +4 -1
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-product-paint.client.d.ts +92 -0
- package/src/lib/graph-product-paint.client.js +196 -0
- package/src/lib/graph-product-paint.client.js.map +1 -0
- package/src/lib/graph-products.d.ts +54 -6
- package/src/lib/graph-products.js +62 -9
- package/src/lib/graph-products.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +12 -12
- package/src/lib/graph-render-model.js +12 -14
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +13 -1
- package/src/lib/graph-visualizer.client.js +27 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +3 -3
- package/src/lib/graph-visualizer.js +13 -12
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-goal-comparison.js +1 -1
- package/src/lib/runtime-goal-comparison.js.map +1 -1
- package/src/lib/runtime-html-page.js +3 -2
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.js +1 -1
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.d.ts +4 -1
- package/src/lib/runtime-viewer-adapter.js +6 -13
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
package/README.md
CHANGED
|
@@ -186,15 +186,22 @@ After upgrading, regenerate old RPC-only JSON with `pnpm nx run architecture:gen
|
|
|
186
186
|
products. An untagged library belongs to every product whose tagged project reaches it over
|
|
187
187
|
`dependsOn` without passing through another tagged project, written per project as
|
|
188
188
|
`"products": [...]` into `dependencies.json`. A published library whose entry points live in other
|
|
189
|
-
repos carries its own tag.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
189
|
+
repos carries its own tag. Every product's legend row is a **toggle** (#1319), and the page
|
|
190
|
+
repaints in the browser from the products that are ON — no regeneration. On every load the first
|
|
191
|
+
2 products in legend (sorted-name) order are ON and the rest OFF; nothing is persisted (no
|
|
192
|
+
localStorage, no URL hash), so the page always resets to that. Given the ON set, a box carrying
|
|
193
|
+
exactly one ON product is a solid fill in that product's color; two or more ON products are
|
|
194
|
+
vertical stripes, one per ON product (the overlap the toggles exist to show); only OFF products
|
|
195
|
+
is light grey (still drawn — hiding boxes is the Filter's job); no product is white with a dashed
|
|
196
|
+
border. The neutral "shared by every product" fill applies only while EVERY product is ON and the
|
|
197
|
+
box carries them all; otherwise it is striped. Each product keeps its fixed palette color (#1244)
|
|
198
|
+
whatever else is ON. The legend counts each product's boxes and how many belong to it alone. A
|
|
199
|
+
dependency that crosses into another product (its target does not carry every product of its
|
|
200
|
+
source) is drawn dashed in the color of the product it depends on while that product is ON, and
|
|
201
|
+
plain otherwise, so product boundaries read at a glance while same-product edges stay plain
|
|
202
|
+
(#1204); the legend counts those edges and lists the `product-tags` rule's allowed matrix
|
|
203
|
+
(`example → openapi, platform`). Which crossings are ALLOWED is
|
|
204
|
+
`product-tags.productDependencies`: generation refuses any other.
|
|
198
205
|
|
|
199
206
|
**Filter** opens a popover of four groups that INTERSECT: the changes of the active Impact
|
|
200
207
|
comparison, headed by its label and carrying the same branch / last-commit toggle (All ·
|
|
@@ -251,8 +258,10 @@ file is **Changed**. Build input sets come from compile dependencies, never runt
|
|
|
251
258
|
Main/detached HEAD compares the last commit; a fresh feature branch compares its fork point only;
|
|
252
259
|
a feature with commits offers both. Missing git/Nx data disables Impact-dependent controls.
|
|
253
260
|
Queues/vendors never claim source ownership or CI status: Runtime/Architecture/Impact retain
|
|
254
|
-
kind colors and shapes; Product uses inherited product colors (a gradient on contextual shapes)
|
|
255
|
-
|
|
261
|
+
kind colors and shapes; Product uses inherited product colors (a gradient on contextual shapes)
|
|
262
|
+
with the same per-product legend toggles as the dependency viewer: the first 2 ON at every load,
|
|
263
|
+
light grey for only-OFF membership, white for no product, neutral only while every product is ON
|
|
264
|
+
and the node carries them all. Runtime services use the
|
|
256
265
|
established nested/striped framework palette and Architecture uses declared roles.
|
|
257
266
|
|
|
258
267
|
Lock from Focus, the sidebar switch, or a box menu pins separate complete incoming/outgoing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.948",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/api-doc-model": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/api-doc-model": "0.4.948",
|
|
24
|
+
"@webpieces/core-util": "0.4.948",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.948",
|
|
26
|
+
"@webpieces/rules-config": "0.4.948",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.948"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@nx/devkit": ">=18.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@webpieces/tooling-testkit": "0.4.
|
|
44
|
+
"@webpieces/tooling-testkit": "0.4.948"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -281,11 +281,14 @@ function reportCycles(projectName, cycles) {
|
|
|
281
281
|
});
|
|
282
282
|
console.error('\nTo fix, break the cycle (extract a shared module, or use an interface).');
|
|
283
283
|
console.error('To exempt a path (generated code, a deliberate bidirectional model), add a pattern to');
|
|
284
|
-
console.error(`"${RULE_NAME}".excludeRegExp in
|
|
284
|
+
console.error(`"${RULE_NAME}".excludeRegExp in its owner rule file. Patterns match paths RELATIVE`);
|
|
285
285
|
console.error('TO THE PROJECT, e.g. "^src/generated/" or "^src/modules/(item|category)/".');
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
// #1318: the BRANCH hatch leads; a future epoch is REPO-WIDE, so it is only a +1 hour last resort.
|
|
287
|
+
const entry = `the "${RULE_NAME}" entry in its owner rule file (the @webpieces/nx-webpieces-rules entry of "rulePacks" in webpieces.config.json, conventionally .webpieces-config/rules/nx-webpieces-rules.json)`;
|
|
288
|
+
const turnOff = new rules_config_1.TurnOffRuleCure();
|
|
289
|
+
console.error('To shelter a known cycle during one refactor, a human can turn the rule off:');
|
|
290
|
+
for (const line of (0, rules_config_1.formatFixOptions)(turnOff.options(entry)))
|
|
291
|
+
console.error(line);
|
|
289
292
|
console.error(`To turn the gate off entirely, set "${RULE_NAME}".mode to "OFF".\n`);
|
|
290
293
|
}
|
|
291
294
|
async function runExecutor(_options, context) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — undeclared, so consumers of the published plugin had no\n * local madge and CI runners corrupted their npx cache fetching it), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `turnOffRuleUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"RUN_EVERY_TIME\", // \"OFF\" disables the gate everywhere\n * \"turnOffRuleUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"turnOffRuleWhileOnBranch\": null, // branch name; while that branch is checked out,\n * // cycles are reported but the gate PASSES\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@db/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate, shouldSkipRule } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'RUN_EVERY_TIME' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n // The full dependency graph: id → its dependency ids. Keys are the ids madge\n // traversed (relative to the base it was invoked with) — used to detect\n // excludeRegExp patterns that match nothing.\n obj(): Record<string, string[]>;\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the universal escape hatches: the turnOffRuleUntilEpoch\n * grace window and turnOffRuleWhileOnBranch. Logs a one-line explanation when a\n * hatch is active.\n */\nfunction isFailingActive(epoch: number | undefined, branch: string | undefined): boolean {\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(\n `\\n⏳ no-file-import-cycles: ${skip.reason}.` +\n '\\n Cycles will be reported but NOT fail the build.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@acme-internal/db\" → [\"libraries/db/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\n/**\n * Realpath a directory, tolerating a non-existent path (returns the input).\n * madge traverses through pnpm symlinks to real paths, and resolvePackageDir\n * already realpaths its result, so projectRoot must be realpath'd too or the\n * computed relative path won't line up with the ids madge emits.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction realpathOrSelf(dir: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort; fall back to the raw path\n try {\n return fs.realpathSync(dir);\n // webpieces-disable catch-error-pattern -- path may not exist yet; use it as-is\n } catch (err: unknown) {\n //const error = toError(err);\n return dir;\n }\n}\n\n/** True if dir contains at least one .ts/.tsx source file anywhere beneath it. */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction hasSourceFiles(dir: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort scan; false on any read failure\n try {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') continue;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (hasSourceFiles(full)) return true;\n } else if (/\\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {\n return true;\n }\n }\n return false;\n // webpieces-disable catch-error-pattern -- unreadable dir; treat as no source\n } catch (err: unknown) {\n //const error = toError(err);\n return false;\n }\n}\n\n/**\n * Build the exclude pattern for one resolved package dir, RELATIVE to the base\n * madge is invoked with (projectRoot). madge matches excludeRegExp against ids\n * relative to that base — e.g. '../../libraries/db/src/x.ts' — so an absolute\n * `^/abs/...` anchor can never match and silently excludes nothing. Returns the\n * relative-anchored pattern. Warns (but still returns the pattern) when the dir\n * holds no source madge would traverse, closing the resolves-but-matches-nothing\n * silent-failure gap.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction buildExcludePattern(dir: string, base: string, pkgName: string): string {\n // madge ids always use forward slashes; normalise path.sep so this holds on Windows.\n const rel = path.relative(base, dir).split(path.sep).join('/');\n if (!hasSourceFiles(dir)) {\n console.warn(\n `⚠️ no-file-import-cycles: excludePackages entry \"${pkgName}\" resolved to \"${dir}\"` +\n ` but that directory contains no .ts/.tsx source — the exclusion will match nothing.`,\n );\n }\n return `^${escapeRegex(rel)}(/|$)`;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nexport function buildMadgeOptions(\n ignoreTypeOnly: boolean,\n excludePackages: string[],\n workspaceRoot: string,\n projectRoot: string,\n // Raw patterns from config, handed to madge verbatim. Matched against ids RELATIVE TO\n // projectRoot (see NoFileImportCyclesConfig.excludeRegExp) — the executor warns on any that\n // match nothing so a mis-anchored (workspace-rooted / absolute) pattern isn't a silent no-op.\n userExcludeRegExp: string[] = [],\n): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES, ...userExcludeRegExp];\n // madge is invoked with projectRoot as its base and emits ids relative to it;\n // realpath it to match resolvePackageDir's realpath'd result under pnpm symlinks.\n const base = realpathOrSelf(projectRoot);\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(buildExcludePattern(dir, base, pkg));\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\n/**\n * The relative-id trap: madge matches excludeRegExp against ids RELATIVE TO\n * projectRoot (e.g. 'src/generated/api.ts'), so a workspace-anchored pattern\n * ('^libraries/apis/foo/src/...') or an absolute one ('^/abs/...') silently\n * matches nothing and the exclusion is a no-op. To make that visible, run madge\n * WITHOUT the user patterns to get the id universe and warn for any pattern that\n * matches none of it (or is not a valid regex). Same \"resolves but matches\n * nothing\" idea buildExcludePattern uses for excludePackages. Only invoked when\n * the consumer actually configured excludeRegExp, so the extra traversal is paid\n * only in the rare case that opts in.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nasync function warnOnUnmatchedExcludeRegExp(\n madge: MadgeFn,\n projectRoot: string,\n baseOptions: MadgeOptions,\n userExcludeRegExp: string[],\n): Promise<void> {\n if (userExcludeRegExp.length === 0) return;\n const universe = Object.keys((await madge(projectRoot, baseOptions)).obj());\n for (const pattern of userExcludeRegExp) {\n let re: RegExp;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- invalid user regex is reported, not thrown\n try {\n re = new RegExp(pattern);\n // webpieces-disable catch-error-pattern -- surface the bad pattern to the consumer, then move on\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: excludeRegExp entry \"${pattern}\" is not a valid regex` +\n ` (${error.message}) — skipping.`,\n );\n continue;\n }\n if (!universe.some((id: string) => re.test(id))) {\n console.warn(\n `⚠️ no-file-import-cycles: excludeRegExp entry \"${pattern}\" matched none of the` +\n ` ${universe.length} traversed file(s) — the exclusion does nothing.` +\n `\\n Patterns match paths RELATIVE TO THE PROJECT (e.g. \"^src/generated/\"),` +\n ` not workspace-rooted or absolute.`,\n );\n }\n }\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To exempt a path (generated code, a deliberate bidirectional model), add a pattern to');\n console.error(`\"${RULE_NAME}\".excludeRegExp in webpieces.config.json. Patterns match paths RELATIVE`);\n console.error('TO THE PROJECT, e.g. \"^src/generated/\" or \"^src/modules/(item|category)/\".');\n console.error('To time-box a known cycle, a human can set \"turnOffRuleUntilEpoch\"');\n console.error(`(epoch seconds) on the \"${RULE_NAME}\" rule in webpieces.config.json,`);\n console.error(`or \"turnOffRuleWhileOnBranch\" (a branch name) to shelter one refactor branch.`);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['turnOffRuleUntilEpoch'] as number | undefined;\n // turnOffRuleWhileOnBranch is required-but-NULLABLE in the config (null = \"always on\"), so narrow\n // to a branch NAME here; anything else means no branch scoping.\n const rawBranch = rule?.options['turnOffRuleWhileOnBranch'];\n const branch = typeof rawBranch === 'string' ? rawBranch : undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n const userExcludeRegExp = (rule?.options['excludeRegExp'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n // baseOptions carries the built-in + excludePackages regexes only; the real run appends the\n // consumer's excludeRegExp, and the warning pass re-uses baseOptions to see what those patterns\n // *would* have to match against (madge's un-user-filtered id universe).\n const baseOptions = buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root, projectRoot);\n const options = buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root, projectRoot, userExcludeRegExp);\n const result = await madge(projectRoot, options);\n const cycles = result.circular();\n\n await warnOnUnmatchedExcludeRegExp(madge, projectRoot, baseOptions, userExcludeRegExp);\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window or branch hatch active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch, branch) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;;AA+NH,8CA8BC;AAgED,8BA+CC;;AAzWD,0DAA6G;AAC7G,+CAAyB;AACzB,mDAA6B;AAC7B,2CAAwC;AAYxC,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAwB1C,SAAS,SAAS;IACd,8DAA8D;IAC9D,MAAM,GAAG,GAAgB,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,KAAyB,EAAE,MAA0B;IAC1E,MAAM,IAAI,GAAG,IAAA,6BAAc,EAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CACP,8BAA8B,IAAI,CAAC,MAAM,GAAG;YACxC,wDAAwD,CAC/D,CAAC;QACF,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,gFAAgF;AAChF,0EAA0E;AAC1E,gFAAgF;AAChF,0EAA0E;AAC1E,MAAM,kBAAkB,GAAG,gEAAgE,CAAC;AAC5F,MAAM,yBAAyB,GAAG,YAAY,CAAC;AAE/C,SAAS,WAAW,CAAC,CAAS;IAC1B,OAAO,CAAC,CAAC,OAAO,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,uBAAuB;IACzB,KAAK,CAA4B;CACpC;AACD,MAAM,YAAY;IACd,eAAe,CAA2B;CAC7C;AAED,iGAAiG;AACjG,SAAS,iBAAiB,CAAC,aAAqB;IAC5C,uGAAuG;IACvG,IAAI,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC;QACrD,OAAO,QAAQ,EAAE,eAAe,EAAE,KAAK,IAAI,IAAI,CAAC;QACpD,+FAA+F;IAC/F,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,SAAiB;IACtC,IAAI,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IACpC,OAAO,GAAG,KAAK,MAAM,EAAE,CAAC;QACpB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9D,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe,EAAE,aAAqB;IAC7D,wEAAwE;IACxE,sIAAsI;IACtI,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QACvF,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,qHAAqH;IACrH,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,wEAAwE;IAC5E,CAAC;IAED,kEAAkE;IAClE,mFAAmF;IACnF,gFAAgF;IAChF,MAAM,aAAa,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CACR,uEAAuE,OAAO,GAAG;YAC7E,qEAAqE,CAC5E,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,2GAA2G;IAC3G,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CACR,wCAAwC,OAAO,QAAQ,QAAQ,GAAG;gBAC9D,8DAA8D,CACrE,CAAC;YACF,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CACR,uEAAuE,OAAO,GAAG;YAC7E,wBAAwB,KAAK,CAAC,OAAO,eAAe,CAC3D,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,iJAAiJ;AACjJ,SAAS,cAAc,CAAC,GAAW;IAC/B,wGAAwG;IACxG,IAAI,CAAC;QACD,OAAO,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAChC,gFAAgF;IAChF,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,OAAO,GAAG,CAAC;IACf,CAAC;AACL,CAAC;AAED,kFAAkF;AAClF,iJAAiJ;AACjJ,SAAS,cAAc,CAAC,GAAW;IAC/B,6GAA6G;IAC7G,IAAI,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC/D,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;gBAAE,SAAS;YAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACtB,IAAI,cAAc,CAAC,IAAI,CAAC;oBAAE,OAAO,IAAI,CAAC;YAC1C,CAAC;iBAAM,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrE,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;QACjB,8EAA8E;IAC9E,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,iJAAiJ;AACjJ,SAAS,mBAAmB,CAAC,GAAW,EAAE,IAAY,EAAE,OAAe;IACnE,qFAAqF;IACrF,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CACR,qDAAqD,OAAO,kBAAkB,GAAG,GAAG;YAChF,qFAAqF,CAC5F,CAAC;IACN,CAAC;IACD,OAAO,IAAI,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC;AACvC,CAAC;AAED,iJAAiJ;AACjJ,SAAgB,iBAAiB,CAC7B,cAAuB,EACvB,eAAyB,EACzB,aAAqB,EACrB,WAAmB;AACnB,sFAAsF;AACtF,4FAA4F;AAC5F,8FAA8F;AAC9F,oBAA8B,EAAE;IAEhC,MAAM,aAAa,GAAG,CAAC,kBAAkB,EAAE,yBAAyB,EAAE,GAAG,iBAAiB,CAAC,CAAC;IAC5F,8EAA8E;IAC9E,kFAAkF;IAClF,MAAM,IAAI,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IACzC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAClD,IAAI,GAAG;YAAE,aAAa,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,OAAO,GAAiB;QAC1B,cAAc,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;QAC7B,aAAa;KAChB,CAAC;IACF,IAAI,cAAc,EAAE,CAAC;QACjB,iFAAiF;QACjF,OAAO,CAAC,gBAAgB,GAAG;YACvB,EAAE,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;YAC7B,GAAG,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;SACjC,CAAC;IACN,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;;;;;;;;;GAUG;AACH,iJAAiJ;AACjJ,KAAK,UAAU,4BAA4B,CACvC,KAAc,EACd,WAAmB,EACnB,WAAyB,EACzB,iBAA2B;IAE3B,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5E,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACtC,IAAI,EAAU,CAAC;QACf,4GAA4G;QAC5G,IAAI,CAAC;YACD,EAAE,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;YAC7B,iGAAiG;QACjG,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,CAAC,IAAI,CACR,mDAAmD,OAAO,wBAAwB;gBAC9E,KAAK,KAAK,CAAC,OAAO,eAAe,CACxC,CAAC;YACF,SAAS;QACb,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CACR,mDAAmD,OAAO,uBAAuB;gBAC7E,IAAI,QAAQ,CAAC,MAAM,kDAAkD;gBACrE,6EAA6E;gBAC7E,oCAAoC,CAC3C,CAAC;QACN,CAAC;IACL,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,WAAmB,EAAE,MAAkB;IACzD,OAAO,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,MAAM,gCAAgC,WAAW,KAAK,CAAC,CAAC;IAC1F,MAAM,CAAC,OAAO,CAAC,CAAC,KAAe,EAAE,CAAS,EAAE,EAAE;QAC1C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC3F,OAAO,CAAC,KAAK,CAAC,uFAAuF,CAAC,CAAC;IACvG,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,uEAAuE,CAAC,CAAC;IACpG,OAAO,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAC5F,mGAAmG;IACnG,MAAM,KAAK,GAAG,QAAQ,SAAS,kLAAkL,CAAC;IAClN,MAAM,OAAO,GAAG,IAAI,8BAAe,EAAE,CAAC;IACtC,OAAO,CAAC,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAC9F,KAAK,MAAM,IAAI,IAAI,IAAA,+BAAgB,EAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjF,OAAO,CAAC,KAAK,CAAC,uCAAuC,SAAS,oBAAoB,CAAC,CAAC;AACxF,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,QAA2C,EAC3C,OAAwB;IAExB,MAAM,MAAM,GAAG,IAAA,8BAAe,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAEzC,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,kBAAkB,SAAS,gBAAgB,CAAC,CAAC;QACzD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,SAAS,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,sBAAsB,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAE/F,MAAM,KAAK,GAAG,IAAI,EAAE,OAAO,CAAC,uBAAuB,CAAuB,CAAC;IAC3E,kGAAkG;IAClG,gEAAgE;IAChE,MAAM,SAAS,GAAG,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,MAAM,cAAc,GAAI,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAyB,IAAI,KAAK,CAAC;IACzF,MAAM,eAAe,GAAI,IAAI,EAAE,OAAO,CAAC,iBAAiB,CAA0B,IAAI,EAAE,CAAC;IACzF,MAAM,iBAAiB,GAAI,IAAI,EAAE,OAAO,CAAC,eAAe,CAA0B,IAAI,EAAE,CAAC;IAEzF,OAAO,CAAC,GAAG,CAAC,kCAAkC,WAAW,YAAY,CAAC,CAAC;IAEvE,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,4FAA4F;IAC5F,gGAAgG;IAChG,wEAAwE;IACxE,MAAM,WAAW,GAAG,iBAAiB,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAClG,MAAM,OAAO,GAAG,iBAAiB,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACjH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAEjC,MAAM,4BAA4B,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAEvF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAElC,yEAAyE;IACzE,OAAO,EAAE,OAAO,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AACxD,CAAC","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — undeclared, so consumers of the published plugin had no\n * local madge and CI runners corrupted their npx cache fetching it), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `turnOffRuleUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"RUN_EVERY_TIME\", // \"OFF\" disables the gate everywhere\n * \"turnOffRuleUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"turnOffRuleWhileOnBranch\": null, // branch name; while that branch is checked out,\n * // cycles are reported but the gate PASSES\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@db/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate, shouldSkipRule, TurnOffRuleCure, formatFixOptions } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'RUN_EVERY_TIME' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n // The full dependency graph: id → its dependency ids. Keys are the ids madge\n // traversed (relative to the base it was invoked with) — used to detect\n // excludeRegExp patterns that match nothing.\n obj(): Record<string, string[]>;\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the universal escape hatches: the turnOffRuleUntilEpoch\n * grace window and turnOffRuleWhileOnBranch. Logs a one-line explanation when a\n * hatch is active.\n */\nfunction isFailingActive(epoch: number | undefined, branch: string | undefined): boolean {\n const skip = shouldSkipRule(epoch, branch);\n if (skip.skip) {\n console.log(\n `\\n⏳ no-file-import-cycles: ${skip.reason}.` +\n '\\n Cycles will be reported but NOT fail the build.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@acme-internal/db\" → [\"libraries/db/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\n/**\n * Realpath a directory, tolerating a non-existent path (returns the input).\n * madge traverses through pnpm symlinks to real paths, and resolvePackageDir\n * already realpaths its result, so projectRoot must be realpath'd too or the\n * computed relative path won't line up with the ids madge emits.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction realpathOrSelf(dir: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort; fall back to the raw path\n try {\n return fs.realpathSync(dir);\n // webpieces-disable catch-error-pattern -- path may not exist yet; use it as-is\n } catch (err: unknown) {\n //const error = toError(err);\n return dir;\n }\n}\n\n/** True if dir contains at least one .ts/.tsx source file anywhere beneath it. */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction hasSourceFiles(dir: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort scan; false on any read failure\n try {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') continue;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (hasSourceFiles(full)) return true;\n } else if (/\\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {\n return true;\n }\n }\n return false;\n // webpieces-disable catch-error-pattern -- unreadable dir; treat as no source\n } catch (err: unknown) {\n //const error = toError(err);\n return false;\n }\n}\n\n/**\n * Build the exclude pattern for one resolved package dir, RELATIVE to the base\n * madge is invoked with (projectRoot). madge matches excludeRegExp against ids\n * relative to that base — e.g. '../../libraries/db/src/x.ts' — so an absolute\n * `^/abs/...` anchor can never match and silently excludes nothing. Returns the\n * relative-anchored pattern. Warns (but still returns the pattern) when the dir\n * holds no source madge would traverse, closing the resolves-but-matches-nothing\n * silent-failure gap.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nfunction buildExcludePattern(dir: string, base: string, pkgName: string): string {\n // madge ids always use forward slashes; normalise path.sep so this holds on Windows.\n const rel = path.relative(base, dir).split(path.sep).join('/');\n if (!hasSourceFiles(dir)) {\n console.warn(\n `⚠️ no-file-import-cycles: excludePackages entry \"${pkgName}\" resolved to \"${dir}\"` +\n ` but that directory contains no .ts/.tsx source — the exclusion will match nothing.`,\n );\n }\n return `^${escapeRegex(rel)}(/|$)`;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nexport function buildMadgeOptions(\n ignoreTypeOnly: boolean,\n excludePackages: string[],\n workspaceRoot: string,\n projectRoot: string,\n // Raw patterns from config, handed to madge verbatim. Matched against ids RELATIVE TO\n // projectRoot (see NoFileImportCyclesConfig.excludeRegExp) — the executor warns on any that\n // match nothing so a mis-anchored (workspace-rooted / absolute) pattern isn't a silent no-op.\n userExcludeRegExp: string[] = [],\n): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES, ...userExcludeRegExp];\n // madge is invoked with projectRoot as its base and emits ids relative to it;\n // realpath it to match resolvePackageDir's realpath'd result under pnpm symlinks.\n const base = realpathOrSelf(projectRoot);\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(buildExcludePattern(dir, base, pkg));\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\n/**\n * The relative-id trap: madge matches excludeRegExp against ids RELATIVE TO\n * projectRoot (e.g. 'src/generated/api.ts'), so a workspace-anchored pattern\n * ('^libraries/apis/foo/src/...') or an absolute one ('^/abs/...') silently\n * matches nothing and the exclusion is a no-op. To make that visible, run madge\n * WITHOUT the user patterns to get the id universe and warn for any pattern that\n * matches none of it (or is not a valid regex). Same \"resolves but matches\n * nothing\" idea buildExcludePattern uses for excludePackages. Only invoked when\n * the consumer actually configured excludeRegExp, so the extra traversal is paid\n * only in the rare case that opts in.\n */\n// webpieces-disable no-function-outside-class -- nx executor module; file is functional by convention (see resolvePackageDir/reportCycles above)\nasync function warnOnUnmatchedExcludeRegExp(\n madge: MadgeFn,\n projectRoot: string,\n baseOptions: MadgeOptions,\n userExcludeRegExp: string[],\n): Promise<void> {\n if (userExcludeRegExp.length === 0) return;\n const universe = Object.keys((await madge(projectRoot, baseOptions)).obj());\n for (const pattern of userExcludeRegExp) {\n let re: RegExp;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- invalid user regex is reported, not thrown\n try {\n re = new RegExp(pattern);\n // webpieces-disable catch-error-pattern -- surface the bad pattern to the consumer, then move on\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: excludeRegExp entry \"${pattern}\" is not a valid regex` +\n ` (${error.message}) — skipping.`,\n );\n continue;\n }\n if (!universe.some((id: string) => re.test(id))) {\n console.warn(\n `⚠️ no-file-import-cycles: excludeRegExp entry \"${pattern}\" matched none of the` +\n ` ${universe.length} traversed file(s) — the exclusion does nothing.` +\n `\\n Patterns match paths RELATIVE TO THE PROJECT (e.g. \"^src/generated/\"),` +\n ` not workspace-rooted or absolute.`,\n );\n }\n }\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To exempt a path (generated code, a deliberate bidirectional model), add a pattern to');\n console.error(`\"${RULE_NAME}\".excludeRegExp in its owner rule file. Patterns match paths RELATIVE`);\n console.error('TO THE PROJECT, e.g. \"^src/generated/\" or \"^src/modules/(item|category)/\".');\n // #1318: the BRANCH hatch leads; a future epoch is REPO-WIDE, so it is only a +1 hour last resort.\n const entry = `the \"${RULE_NAME}\" entry in its owner rule file (the @webpieces/nx-webpieces-rules entry of \"rulePacks\" in webpieces.config.json, conventionally .webpieces-config/rules/nx-webpieces-rules.json)`;\n const turnOff = new TurnOffRuleCure();\n console.error('To shelter a known cycle during one refactor, a human can turn the rule off:');\n for (const line of formatFixOptions(turnOff.options(entry))) console.error(line);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['turnOffRuleUntilEpoch'] as number | undefined;\n // turnOffRuleWhileOnBranch is required-but-NULLABLE in the config (null = \"always on\"), so narrow\n // to a branch NAME here; anything else means no branch scoping.\n const rawBranch = rule?.options['turnOffRuleWhileOnBranch'];\n const branch = typeof rawBranch === 'string' ? rawBranch : undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n const userExcludeRegExp = (rule?.options['excludeRegExp'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n // baseOptions carries the built-in + excludePackages regexes only; the real run appends the\n // consumer's excludeRegExp, and the warning pass re-uses baseOptions to see what those patterns\n // *would* have to match against (madge's un-user-filtered id universe).\n const baseOptions = buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root, projectRoot);\n const options = buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root, projectRoot, userExcludeRegExp);\n const result = await madge(projectRoot, options);\n const cycles = result.circular();\n\n await warnOnUnmatchedExcludeRegExp(madge, projectRoot, baseOptions, userExcludeRegExp);\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window or branch hatch active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch, branch) };\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type { ApiDocMode } from './configs/api-doc-rules-config';
|
|
|
28
28
|
export { ApiClientPackagesEntry, ApiLibDependenciesConfig, ApiLibPathConfig, FrameworkFolderEntry, FrameworkFolderConfig, ProductDependencyEntry, ProductTagsConfig } from './configs/tag-truth-configs';
|
|
29
29
|
export { GraphRenderModel, RenderNode, RenderEdge, NodeTags } from './lib/graph-render-model';
|
|
30
30
|
export { GraphMode, NodeModeDots, LegendFacts, ProductCount } from './lib/graph-color-modes';
|
|
31
|
-
export { ProductMembership, RuntimeProductMembership, ProductPalette, ProductColor, ProductPaletteSlot, PRODUCT_PALETTE_SLOTS, ProductBoundary, } from './lib/graph-products';
|
|
31
|
+
export { ProductMembership, RuntimeProductMembership, ProductPalette, ProductColor, ProductPaletteSlot, PRODUCT_PALETTE_SLOTS, ProductBoundary, ProductTemplate, ProductTemplateKind, } from './lib/graph-products';
|
|
32
32
|
export { ProductDependencyMatrix } from './lib/product-dependencies';
|
|
33
33
|
export { ProductResolver, ProductResolution, PRODUCT_TAG_PREFIX } from './lib/product-resolver';
|
|
34
34
|
export { EntryPointLevels } from './lib/graph-entry-levels';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EntryPointLevels = exports.PRODUCT_TAG_PREFIX = exports.ProductResolution = exports.ProductResolver = exports.ProductDependencyMatrix = exports.ProductBoundary = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = exports.ProductPalette = exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductCount = exports.LegendFacts = exports.NodeModeDots = exports.GraphMode = exports.NodeTags = exports.RenderEdge = exports.RenderNode = exports.GraphRenderModel = exports.ProductTagsConfig = exports.ProductDependencyEntry = exports.FrameworkFolderConfig = exports.FrameworkFolderEntry = exports.ApiLibPathConfig = exports.ApiLibDependenciesConfig = exports.ApiClientPackagesEntry = exports.ApiRulesForMcpConfig = exports.ApiRulesForOpenApiConfig = exports.API_DOC_MODES = exports.NoRootUnionApiTypeConfig = exports.WiringFormatConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.MissingDesignAnnotationConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.STRUCTURAL_MODES = exports.createNodesV2 = void 0;
|
|
3
|
+
exports.EntryPointLevels = exports.PRODUCT_TAG_PREFIX = exports.ProductResolution = exports.ProductResolver = exports.ProductDependencyMatrix = exports.ProductTemplateKind = exports.ProductTemplate = exports.ProductBoundary = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = exports.ProductPalette = exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductCount = exports.LegendFacts = exports.NodeModeDots = exports.GraphMode = exports.NodeTags = exports.RenderEdge = exports.RenderNode = exports.GraphRenderModel = exports.ProductTagsConfig = exports.ProductDependencyEntry = exports.FrameworkFolderConfig = exports.FrameworkFolderEntry = exports.ApiLibPathConfig = exports.ApiLibDependenciesConfig = exports.ApiClientPackagesEntry = exports.ApiRulesForMcpConfig = exports.ApiRulesForOpenApiConfig = exports.API_DOC_MODES = exports.NoRootUnionApiTypeConfig = exports.WiringFormatConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.MissingDesignAnnotationConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.STRUCTURAL_MODES = exports.createNodesV2 = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
tslib_1.__exportStar(require("./lib/graph-generator"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./lib/graph-sorter"), exports);
|
|
@@ -65,6 +65,8 @@ Object.defineProperty(exports, "ProductColor", { enumerable: true, get: function
|
|
|
65
65
|
Object.defineProperty(exports, "ProductPaletteSlot", { enumerable: true, get: function () { return graph_products_1.ProductPaletteSlot; } });
|
|
66
66
|
Object.defineProperty(exports, "PRODUCT_PALETTE_SLOTS", { enumerable: true, get: function () { return graph_products_1.PRODUCT_PALETTE_SLOTS; } });
|
|
67
67
|
Object.defineProperty(exports, "ProductBoundary", { enumerable: true, get: function () { return graph_products_1.ProductBoundary; } });
|
|
68
|
+
Object.defineProperty(exports, "ProductTemplate", { enumerable: true, get: function () { return graph_products_1.ProductTemplate; } });
|
|
69
|
+
Object.defineProperty(exports, "ProductTemplateKind", { enumerable: true, get: function () { return graph_products_1.ProductTemplateKind; } });
|
|
68
70
|
var product_dependencies_1 = require("./lib/product-dependencies");
|
|
69
71
|
Object.defineProperty(exports, "ProductDependencyMatrix", { enumerable: true, get: function () { return product_dependencies_1.ProductDependencyMatrix; } });
|
|
70
72
|
var product_resolver_1 = require("./lib/product-resolver");
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,+DAAqC;AACrC,mEAAyC;AACzC,6EAAmD;AACnD,6DAAmC;AACnC,iEAAuC;AACvC,6DAAmC;AACnC,2EAAiD;AACjD,uEAA6C;AAC7C,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC;AAExE,uDAAyU;AAAhU,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,8GAAA,cAAc,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,6HAAA,6BAA6B,OAAA;AAAE,mIAAA,mCAAmC,OAAA;AAAE,kIAAA,kCAAkC,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAExS,uEAAoE;AAA3D,0HAAA,kBAAkB,OAAA;AAC3B,uEAA0E;AAAjE,gIAAA,wBAAwB,OAAA;AACjC,uEAA+G;AAAtG,qHAAA,aAAa,OAAA;AAAE,gIAAA,wBAAwB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAEtE,iEAAyM;AAAhM,2HAAA,sBAAsB,OAAA;AAAE,6HAAA,wBAAwB,OAAA;AAAE,qHAAA,gBAAgB,OAAA;AAAE,yHAAA,oBAAoB,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AAAE,2HAAA,sBAAsB,OAAA;AAAE,sHAAA,iBAAiB,OAAA;AAEnK,+DAA8F;AAArF,sHAAA,gBAAgB,OAAA;AAAE,gHAAA,UAAU,OAAA;AAAE,gHAAA,UAAU,OAAA;AAAE,8GAAA,QAAQ,OAAA;AAC3D,6DAA6F;AAApF,8GAAA,SAAS,OAAA;AAAE,iHAAA,YAAY,OAAA;AAAE,gHAAA,WAAW,OAAA;AAAE,iHAAA,YAAY,OAAA;AAC3D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,+DAAqC;AACrC,mEAAyC;AACzC,6EAAmD;AACnD,6DAAmC;AACnC,iEAAuC;AACvC,6DAAmC;AACnC,2EAAiD;AACjD,uEAA6C;AAC7C,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC;AAExE,uDAAyU;AAAhU,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,8GAAA,cAAc,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,6HAAA,6BAA6B,OAAA;AAAE,mIAAA,mCAAmC,OAAA;AAAE,kIAAA,kCAAkC,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAExS,uEAAoE;AAA3D,0HAAA,kBAAkB,OAAA;AAC3B,uEAA0E;AAAjE,gIAAA,wBAAwB,OAAA;AACjC,uEAA+G;AAAtG,qHAAA,aAAa,OAAA;AAAE,gIAAA,wBAAwB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAEtE,iEAAyM;AAAhM,2HAAA,sBAAsB,OAAA;AAAE,6HAAA,wBAAwB,OAAA;AAAE,qHAAA,gBAAgB,OAAA;AAAE,yHAAA,oBAAoB,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AAAE,2HAAA,sBAAsB,OAAA;AAAE,sHAAA,iBAAiB,OAAA;AAEnK,+DAA8F;AAArF,sHAAA,gBAAgB,OAAA;AAAE,gHAAA,UAAU,OAAA;AAAE,gHAAA,UAAU,OAAA;AAAE,8GAAA,QAAQ,OAAA;AAC3D,6DAA6F;AAApF,8GAAA,SAAS,OAAA;AAAE,iHAAA,YAAY,OAAA;AAAE,gHAAA,WAAW,OAAA;AAAE,iHAAA,YAAY,OAAA;AAC3D,uDAU8B;AAT1B,mHAAA,iBAAiB,OAAA;AACjB,0HAAA,wBAAwB,OAAA;AACxB,gHAAA,cAAc,OAAA;AACd,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,uHAAA,qBAAqB,OAAA;AACrB,iHAAA,eAAe,OAAA;AACf,iHAAA,eAAe,OAAA;AACf,qHAAA,mBAAmB,OAAA;AAEvB,mEAAqE;AAA5D,+HAAA,uBAAuB,OAAA;AAChC,2DAAgG;AAAvF,mHAAA,eAAe,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAAE,sHAAA,kBAAkB,OAAA;AAC/D,+DAA4D;AAAnD,sHAAA,gBAAgB,OAAA","sourcesContent":["export * from './lib/graph-generator';\nexport * from './lib/graph-sorter';\nexport * from './lib/graph-comparator';\nexport * from './lib/package-validator';\nexport * from './lib/graph-loader';\nexport * from './lib/graph-visualizer';\nexport * from './lib/graph-metadata';\nexport * from './lib/framework-resolver';\nexport * from './lib/runtime-participant-resolver';\nexport * from './lib/project-info';\nexport * from './lib/responsibilities';\nexport * from './lib/di-graph/dot';\nexport * from './lib/di-graph/design-visualizer';\nexport * from './lib/di-graph/design-finder';\nimport { createNodesV2 } from './plugin';\nexport { createNodesV2 };\nexport default { name: '@webpieces/nx-webpieces-rules', createNodesV2 };\n\nexport { STRUCTURAL_MODES, NoFileImportCyclesConfig, RuntimeArchitectureConfig, NxWiringConfig, DiGraphConfig, MissingDesignAnnotationConfig, ValidateArchitectureUnchangedConfig, ValidateNoArchitectureCyclesConfig, ValidatePackageJsonConfig, ValidateVersionsLockedConfig, ValidateEslintSyncConfig } from './configs/rule-configs';\nexport type { StructuralMode } from './configs/rule-configs';\nexport { WiringFormatConfig } from './configs/wiring-format-config';\nexport { NoRootUnionApiTypeConfig } from './configs/no-root-union-config';\nexport { API_DOC_MODES, ApiRulesForOpenApiConfig, ApiRulesForMcpConfig } from './configs/api-doc-rules-config';\nexport type { ApiDocMode } from './configs/api-doc-rules-config';\nexport { ApiClientPackagesEntry, ApiLibDependenciesConfig, ApiLibPathConfig, FrameworkFolderEntry, FrameworkFolderConfig, ProductDependencyEntry, ProductTagsConfig } from './configs/tag-truth-configs';\n\nexport { GraphRenderModel, RenderNode, RenderEdge, NodeTags } from './lib/graph-render-model';\nexport { GraphMode, NodeModeDots, LegendFacts, ProductCount } from './lib/graph-color-modes';\nexport {\n ProductMembership,\n RuntimeProductMembership,\n ProductPalette,\n ProductColor,\n ProductPaletteSlot,\n PRODUCT_PALETTE_SLOTS,\n ProductBoundary,\n ProductTemplate,\n ProductTemplateKind,\n} from './lib/graph-products';\nexport { ProductDependencyMatrix } from './lib/product-dependencies';\nexport { ProductResolver, ProductResolution, PRODUCT_TAG_PREFIX } from './lib/product-resolver';\nexport { EntryPointLevels } from './lib/graph-entry-levels';\n"]}
|
|
@@ -26,15 +26,18 @@
|
|
|
26
26
|
* grey. Which box is which is only known per checkout, so every box carries every
|
|
27
27
|
* variant and the page picks one from the sidecar (graph-impact.ts). The state is
|
|
28
28
|
* never written on the box: color + legend + the "why" panel a click opens.
|
|
29
|
-
* - PRODUCT "which products it belongs to" (#1179, graph-products.ts).
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* product is
|
|
33
|
-
*
|
|
34
|
-
* products
|
|
29
|
+
* - PRODUCT "which products it belongs to" (#1179, graph-products.ts). Each product's legend
|
|
30
|
+
* row is a toggle; the first two (sorted-name order) are ON at every load, and the
|
|
31
|
+
* page repaints in the browser from the ON set (#1319, graph-product-paint.client.ts):
|
|
32
|
+
* exactly one ON product is a solid fill in that product's fixed palette color; two
|
|
33
|
+
* or more ON are the same vertical stripes the multi-runtime box uses, one per ON
|
|
34
|
+
* product; a box carrying only OFF products is light grey; a box in no product is
|
|
35
|
+
* white with a dashed border. The neutral "shared by every product" fill applies
|
|
36
|
+
* only while EVERY product is ON and the box carries them all. The Filter HIDES
|
|
37
|
+
* other products' boxes; this mode keeps every box and makes ownership visible.
|
|
35
38
|
*/
|
|
36
39
|
import { KnownFramework } from '@webpieces/rules-sdk';
|
|
37
|
-
import { ProductPalette } from './graph-products';
|
|
40
|
+
import { ProductPalette, ProductTemplate } from './graph-products';
|
|
38
41
|
import type { ProductDependencyEntry } from '../configs/tag-truth-configs';
|
|
39
42
|
export declare enum GraphMode {
|
|
40
43
|
RUNTIME = "runtime",
|
|
@@ -145,8 +148,11 @@ export declare class NodeModeDots {
|
|
|
145
148
|
readonly unexplained: string;
|
|
146
149
|
readonly buildInput: string;
|
|
147
150
|
readonly untouched: string;
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
/** Product mode's statement depends on which products are ON, so the page writes it (#1319). */
|
|
152
|
+
readonly product: ProductTemplate;
|
|
153
|
+
constructor(runtime: string, architecture: string, touched: string, dependent: string, implicit: string, rootFile: string, unexplained: string, buildInput: string, untouched: string,
|
|
154
|
+
/** Product mode's statement depends on which products are ON, so the page writes it (#1319). */
|
|
155
|
+
product: ProductTemplate);
|
|
150
156
|
}
|
|
151
157
|
/** How many drawn boxes one product holds, and how many of them no other product does. */
|
|
152
158
|
export declare class ProductCount {
|
|
@@ -193,7 +199,7 @@ export declare class NodeModeStyler {
|
|
|
193
199
|
bases(frameworks: string[]): KnownFramework[];
|
|
194
200
|
/** The specialization drawn inside `base`, if the set carries one. */
|
|
195
201
|
specialization(base: KnownFramework, frameworks: string[]): FrameworkStyle | undefined;
|
|
196
|
-
dots(facts: NodeFacts
|
|
202
|
+
dots(facts: NodeFacts): NodeModeDots;
|
|
197
203
|
/** Implicitly touched: thin alternating violet stripes (a hatch), so it never reads as amber. */
|
|
198
204
|
private hatched;
|
|
199
205
|
/** Fold one drawn box into the legend facts. */
|
|
@@ -222,8 +228,8 @@ export declare class NodeModeStyler {
|
|
|
222
228
|
/** A solid fill, the three lines on it. `extra` carries border attributes. */
|
|
223
229
|
private plain;
|
|
224
230
|
/**
|
|
225
|
-
* Product mode (#1179):
|
|
226
|
-
*
|
|
231
|
+
* Product mode (#1179): the statement's head and its label in both text colors. Which fill a box
|
|
232
|
+
* gets depends on the products switched ON in the legend, so the page decides it (#1319).
|
|
227
233
|
*/
|
|
228
234
|
private product;
|
|
229
235
|
/**
|
|
@@ -27,12 +27,15 @@
|
|
|
27
27
|
* grey. Which box is which is only known per checkout, so every box carries every
|
|
28
28
|
* variant and the page picks one from the sidecar (graph-impact.ts). The state is
|
|
29
29
|
* never written on the box: color + legend + the "why" panel a click opens.
|
|
30
|
-
* - PRODUCT "which products it belongs to" (#1179, graph-products.ts).
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* product is
|
|
34
|
-
*
|
|
35
|
-
* products
|
|
30
|
+
* - PRODUCT "which products it belongs to" (#1179, graph-products.ts). Each product's legend
|
|
31
|
+
* row is a toggle; the first two (sorted-name order) are ON at every load, and the
|
|
32
|
+
* page repaints in the browser from the ON set (#1319, graph-product-paint.client.ts):
|
|
33
|
+
* exactly one ON product is a solid fill in that product's fixed palette color; two
|
|
34
|
+
* or more ON are the same vertical stripes the multi-runtime box uses, one per ON
|
|
35
|
+
* product; a box carrying only OFF products is light grey; a box in no product is
|
|
36
|
+
* white with a dashed border. The neutral "shared by every product" fill applies
|
|
37
|
+
* only while EVERY product is ON and the box carries them all. The Filter HIDES
|
|
38
|
+
* other products' boxes; this mode keeps every box and makes ownership visible.
|
|
36
39
|
*/
|
|
37
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
41
|
exports.NodeModeStyler = exports.LegendFacts = exports.ProductCount = exports.NodeModeDots = exports.NodeFacts = exports.IMPACT_UNTOUCHED = exports.IMPACT_BUILD_INPUT_BORDER = exports.IMPACT_BUILD_INPUT = exports.IMPACT_UNEXPLAINED_BORDER = exports.IMPACT_ROOT_FILE_BORDER = exports.IMPACT_ROOT_FILE = exports.IMPACT_IMPLICIT_BORDER = exports.IMPACT_IMPLICIT_GAP = exports.IMPACT_IMPLICIT = exports.IMPACT_DEPENDENT = exports.IMPACT_TOUCHED = exports.ROLE_STYLES = exports.RoleStyle = exports.STRIPED_CLASS = exports.NO_RUNTIME_COLOR = exports.FRAMEWORK_ORDER = exports.BASE_ORDER = exports.FRAMEWORK_STYLES = exports.FrameworkStyle = exports.LIGHT_TEXT = exports.DARK_TEXT = exports.RUNTIME_GRAPH_MODES = exports.GRAPH_MODES = exports.ModeInfo = exports.GraphMode = void 0;
|
|
@@ -216,7 +219,9 @@ class NodeModeDots {
|
|
|
216
219
|
buildInput;
|
|
217
220
|
untouched;
|
|
218
221
|
product;
|
|
219
|
-
constructor(runtime, architecture, touched, dependent, implicit, rootFile, unexplained, buildInput, untouched,
|
|
222
|
+
constructor(runtime, architecture, touched, dependent, implicit, rootFile, unexplained, buildInput, untouched,
|
|
223
|
+
/** Product mode's statement depends on which products are ON, so the page writes it (#1319). */
|
|
224
|
+
product) {
|
|
220
225
|
this.runtime = runtime;
|
|
221
226
|
this.architecture = architecture;
|
|
222
227
|
this.touched = touched;
|
|
@@ -307,8 +312,8 @@ class NodeModeStyler {
|
|
|
307
312
|
}
|
|
308
313
|
return undefined;
|
|
309
314
|
}
|
|
310
|
-
dots(facts
|
|
311
|
-
return new NodeModeDots(this.runtime(facts), this.plain(facts, this.roleStyle(facts.role).fill, this.roleStyle(facts.role).fontColor, ''), this.plain(facts, exports.IMPACT_TOUCHED, exports.DARK_TEXT, ''), this.plain(facts, exports.IMPACT_DEPENDENT, exports.DARK_TEXT, `, color="${exports.IMPACT_TOUCHED}", penwidth=2`), this.hatched(facts), this.plain(facts, exports.IMPACT_ROOT_FILE, exports.DARK_TEXT, `, color="${exports.IMPACT_ROOT_FILE_BORDER}", penwidth=2`), this.plain(facts, exports.IMPACT_DEPENDENT, exports.DARK_TEXT, `, color="${exports.IMPACT_UNEXPLAINED_BORDER}", penwidth=1.5`, 'filled,dashed'), this.plain(facts, exports.IMPACT_BUILD_INPUT, exports.DARK_TEXT, `, color="${exports.IMPACT_BUILD_INPUT_BORDER}", penwidth=1.5`, 'filled,dashed'), this.plain(facts, exports.IMPACT_UNTOUCHED, IMPACT_UNTOUCHED_TEXT, ', color="#b4b9c4"'), this.product(facts
|
|
315
|
+
dots(facts) {
|
|
316
|
+
return new NodeModeDots(this.runtime(facts), this.plain(facts, this.roleStyle(facts.role).fill, this.roleStyle(facts.role).fontColor, ''), this.plain(facts, exports.IMPACT_TOUCHED, exports.DARK_TEXT, ''), this.plain(facts, exports.IMPACT_DEPENDENT, exports.DARK_TEXT, `, color="${exports.IMPACT_TOUCHED}", penwidth=2`), this.hatched(facts), this.plain(facts, exports.IMPACT_ROOT_FILE, exports.DARK_TEXT, `, color="${exports.IMPACT_ROOT_FILE_BORDER}", penwidth=2`), this.plain(facts, exports.IMPACT_DEPENDENT, exports.DARK_TEXT, `, color="${exports.IMPACT_UNEXPLAINED_BORDER}", penwidth=1.5`, 'filled,dashed'), this.plain(facts, exports.IMPACT_BUILD_INPUT, exports.DARK_TEXT, `, color="${exports.IMPACT_BUILD_INPUT_BORDER}", penwidth=1.5`, 'filled,dashed'), this.plain(facts, exports.IMPACT_UNTOUCHED, IMPACT_UNTOUCHED_TEXT, ', color="#b4b9c4"'), this.product(facts));
|
|
312
317
|
}
|
|
313
318
|
/** Implicitly touched: thin alternating violet stripes (a hatch), so it never reads as amber. */
|
|
314
319
|
hatched(facts) {
|
|
@@ -395,18 +400,11 @@ class NodeModeStyler {
|
|
|
395
400
|
return this.statement(facts, `style="${style}", fillcolor="${fill}", fontcolor="${fontColor}"${extra}`, this.labelText(facts, fontColor));
|
|
396
401
|
}
|
|
397
402
|
/**
|
|
398
|
-
* Product mode (#1179):
|
|
399
|
-
*
|
|
403
|
+
* Product mode (#1179): the statement's head and its label in both text colors. Which fill a box
|
|
404
|
+
* gets depends on the products switched ON in the legend, so the page decides it (#1319).
|
|
400
405
|
*/
|
|
401
|
-
product(facts
|
|
402
|
-
|
|
403
|
-
return this.plain(facts, graph_products_1.PRODUCT_NONE_FILL, exports.DARK_TEXT, `, color="${graph_products_1.PRODUCT_NONE_BORDER}", penwidth=1.5`, 'filled,dashed');
|
|
404
|
-
if (palette.sharedByAll(facts.products))
|
|
405
|
-
return this.plain(facts, graph_products_1.PRODUCT_SHARED_FILL, exports.DARK_TEXT, '');
|
|
406
|
-
const colors = facts.products.map((product) => palette.colorOf(product));
|
|
407
|
-
if (colors.length === 1)
|
|
408
|
-
return this.plain(facts, colors[0].color, colors[0].fontColor, '');
|
|
409
|
-
return this.statement(facts, `${this.stripes(colors.map((color) => color.color))}, fontcolor="${exports.DARK_TEXT}"`, this.labelText(facts, exports.DARK_TEXT));
|
|
406
|
+
product(facts) {
|
|
407
|
+
return new graph_products_1.ProductTemplate(graph_products_1.ProductTemplateKind.BOX, ` "${(0, dot_syntax_1.dotValue)(facts.nodeId)}" [`, this.labelText(facts, exports.DARK_TEXT), this.labelText(facts, exports.LIGHT_TEXT));
|
|
410
408
|
}
|
|
411
409
|
/**
|
|
412
410
|
* Runtime mode. No runtime, or one runtime with no specialization: a solid fill. One runtime with a
|