mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
chore: remove maintainability guardrails checks
Drop the Maintainability Guardrails docs section and remove the fan-in/runtime-cycle guardrail scripts from local and CI workflows so contributor guidance matches current validation lanes.
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -38,11 +38,6 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Maintainability guardrails (fail-fast)
|
|
||||||
run: |
|
|
||||||
bun run check:main-fanin:strict
|
|
||||||
bun run check:runtime-cycles:strict
|
|
||||||
|
|
||||||
- name: Build (TypeScript check)
|
- name: Build (TypeScript check)
|
||||||
# Keep explicit typecheck for fast fail before full build/bundle.
|
# Keep explicit typecheck for fast fail before full build/bundle.
|
||||||
run: bun run tsc --noEmit
|
run: bun run tsc --noEmit
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
id: TASK-111
|
||||||
|
title: Remove Maintainability Guardrails docs section and related guardrail code
|
||||||
|
status: Done
|
||||||
|
assignee: []
|
||||||
|
created_date: '2026-02-23 03:37'
|
||||||
|
updated_date: '2026-02-23 03:40'
|
||||||
|
labels:
|
||||||
|
- docs
|
||||||
|
- cleanup
|
||||||
|
- guardrails
|
||||||
|
dependencies: []
|
||||||
|
priority: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
User requested removing the docs section labeled "Maintainability Guardrails" and deleting associated project code/commands for those guardrails so docs and scripts stay consistent.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Docs no longer contain the Maintainability Guardrails section shown in the request.
|
||||||
|
- [x] #2 Commands and code associated specifically with that removed guardrails section are removed from scripts/config where applicable.
|
||||||
|
- [x] #3 Project references remain consistent (no stale mentions of removed guardrails commands).
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Removed the `## Maintainability Guardrails` section from `docs/development.md` including strict command examples and troubleshooting bullets.
|
||||||
|
|
||||||
|
Removed guardrail scripts from `package.json`: `check:main-fanin`, `check:main-fanin:strict`, `check:runtime-cycles`, `check:runtime-cycles:strict`.
|
||||||
|
|
||||||
|
Deleted associated script code and fixtures: `scripts/check-main-runtime-fanin.ts`, `scripts/check-runtime-cycles.ts`, `scripts/check-runtime-cycles.test.ts`, and `scripts/fixtures/runtime-cycles/**`.
|
||||||
|
|
||||||
|
Removed CI fail-fast guardrail step from `.github/workflows/ci.yml` that invoked strict fan-in/runtime-cycle checks.
|
||||||
|
|
||||||
|
Validation: `bun run tsc --noEmit` and `bun run docs:build` passed.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
Removed the Maintainability Guardrails docs section and fully removed the related fan-in/runtime-cycle guardrail implementation from scripts, package commands, CI wiring, and fixtures. The repository now has no active references to those guardrail commands in development docs, package scripts, or CI workflow.
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
@@ -83,25 +83,6 @@ bun run test:core:dist # optional full dist core suite
|
|||||||
bun run test:subtitle:dist # optional smoke lane for subtitle dist regressions
|
bun run test:subtitle:dist # optional smoke lane for subtitle dist regressions
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maintainability Guardrails
|
|
||||||
|
|
||||||
Run guardrails locally before opening a PR:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun run check:main-fanin:strict
|
|
||||||
bun run check:runtime-cycles:strict
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected success output includes:
|
|
||||||
|
|
||||||
- `[OK] main runtime fan-in (strict) — ...`
|
|
||||||
- `[OK] runtime cycle check (strict) - ... no cycles detected`
|
|
||||||
|
|
||||||
Troubleshooting guardrail failures:
|
|
||||||
|
|
||||||
- Main fan-in failure: move runtime imports behind `src/main/runtime/domains/*` or composer barrels.
|
|
||||||
- Runtime cycle failure: break bidirectional imports by extracting shared helpers into leaf modules.
|
|
||||||
|
|
||||||
## Config Generation
|
## Config Generation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -16,10 +16,6 @@
|
|||||||
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
|
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"check:main-fanin": "bun run scripts/check-main-runtime-fanin.ts",
|
|
||||||
"check:main-fanin:strict": "bun run scripts/check-main-runtime-fanin.ts --strict",
|
|
||||||
"check:runtime-cycles": "bun run scripts/check-runtime-cycles.ts",
|
|
||||||
"check:runtime-cycles:strict": "bun run scripts/check-runtime-cycles.ts --strict",
|
|
||||||
"test:config:src": "bun test src/config/config.test.ts src/config/path-resolution.test.ts src/config/resolve/anki-connect.test.ts src/config/resolve/subtitle-style.test.ts src/config/resolve/jellyfin.test.ts src/config/definitions/domain-registry.test.ts",
|
"test:config:src": "bun test src/config/config.test.ts src/config/path-resolution.test.ts src/config/resolve/anki-connect.test.ts src/config/resolve/subtitle-style.test.ts src/config/resolve/jellyfin.test.ts src/config/definitions/domain-registry.test.ts",
|
||||||
"test:config:dist": "node --test dist/config/config.test.js dist/config/path-resolution.test.js dist/config/resolve/anki-connect.test.js dist/config/resolve/subtitle-style.test.js dist/config/resolve/jellyfin.test.js dist/config/definitions/domain-registry.test.js",
|
"test:config:dist": "node --test dist/config/config.test.js dist/config/path-resolution.test.js dist/config/resolve/anki-connect.test.js dist/config/resolve/subtitle-style.test.js dist/config/resolve/jellyfin.test.js dist/config/definitions/domain-registry.test.js",
|
||||||
"test:config:smoke:dist": "node --test dist/config/path-resolution.test.js",
|
"test:config:smoke:dist": "node --test dist/config/path-resolution.test.js",
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
type ParsedArgs = {
|
|
||||||
strict: boolean;
|
|
||||||
uniquePathLimit: number;
|
|
||||||
importLineLimit: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_UNIQUE_PATH_LIMIT = 11;
|
|
||||||
const DEFAULT_IMPORT_LINE_LIMIT = 110;
|
|
||||||
const MAIN_PATH = path.join(process.cwd(), 'src', 'main.ts');
|
|
||||||
|
|
||||||
function parseArgs(argv: string[]): ParsedArgs {
|
|
||||||
let strict = false;
|
|
||||||
let uniquePathLimit = DEFAULT_UNIQUE_PATH_LIMIT;
|
|
||||||
let importLineLimit = DEFAULT_IMPORT_LINE_LIMIT;
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i];
|
|
||||||
if (arg === '--strict') {
|
|
||||||
strict = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--unique-path-limit') {
|
|
||||||
const raw = argv[i + 1];
|
|
||||||
const parsed = Number.parseInt(raw ?? '', 10);
|
|
||||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
||||||
throw new Error(`Invalid --unique-path-limit value: ${raw ?? '<missing>'}`);
|
|
||||||
}
|
|
||||||
uniquePathLimit = parsed;
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--import-line-limit') {
|
|
||||||
const raw = argv[i + 1];
|
|
||||||
const parsed = Number.parseInt(raw ?? '', 10);
|
|
||||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
||||||
throw new Error(`Invalid --import-line-limit value: ${raw ?? '<missing>'}`);
|
|
||||||
}
|
|
||||||
importLineLimit = parsed;
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { strict, uniquePathLimit, importLineLimit };
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectRuntimeImportStats(source: string): {
|
|
||||||
importLines: number;
|
|
||||||
uniquePaths: string[];
|
|
||||||
} {
|
|
||||||
const pathMatches = Array.from(source.matchAll(/from '(\.\/main\/runtime[^']*)';/g)).map(
|
|
||||||
(match) => match[1],
|
|
||||||
);
|
|
||||||
const uniquePaths = new Set<string>();
|
|
||||||
|
|
||||||
for (const runtimeImportPath of pathMatches) {
|
|
||||||
uniquePaths.add(runtimeImportPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
importLines: pathMatches.length,
|
|
||||||
uniquePaths: Array.from(uniquePaths).sort(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function main(): void {
|
|
||||||
const { strict, uniquePathLimit, importLineLimit } = parseArgs(process.argv.slice(2));
|
|
||||||
const source = fs.readFileSync(MAIN_PATH, 'utf8');
|
|
||||||
const { importLines, uniquePaths } = collectRuntimeImportStats(source);
|
|
||||||
const overUniquePathLimit = uniquePaths.length > uniquePathLimit;
|
|
||||||
const overImportLineLimit = importLines > importLineLimit;
|
|
||||||
const hasFailure = overUniquePathLimit || overImportLineLimit;
|
|
||||||
const mode = strict ? 'strict' : 'warning';
|
|
||||||
|
|
||||||
if (!hasFailure) {
|
|
||||||
console.log(
|
|
||||||
`[OK] main runtime fan-in (${mode}) — ${importLines} import lines, ${uniquePaths.length} unique runtime paths`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = strict ? '[FAIL]' : '[WARN]';
|
|
||||||
console.log(
|
|
||||||
`${heading} main runtime fan-in (${mode}) — ${importLines} import lines, ${uniquePaths.length} unique runtime paths`,
|
|
||||||
);
|
|
||||||
console.log(` limits: import lines <= ${importLineLimit}, unique paths <= ${uniquePathLimit}`);
|
|
||||||
console.log(' runtime import paths:');
|
|
||||||
for (const runtimeImportPath of uniquePaths) {
|
|
||||||
console.log(` - ${runtimeImportPath}`);
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
' Hint: keep main.ts focused on boot wiring; move domain imports behind domain barrels/registries.',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (strict) {
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { spawnSync } from 'node:child_process';
|
|
||||||
import { runRuntimeCycleCheck } from './check-runtime-cycles';
|
|
||||||
|
|
||||||
type CliResult = {
|
|
||||||
status: number | null;
|
|
||||||
stdout: string;
|
|
||||||
stderr: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CHECKER_PATH = path.join(process.cwd(), 'scripts', 'check-runtime-cycles.ts');
|
|
||||||
const FIXTURE_ROOT = path.join('scripts', 'fixtures', 'runtime-cycles');
|
|
||||||
|
|
||||||
function runCheckerCli(args: string[]): CliResult {
|
|
||||||
const result = spawnSync('bun', ['run', CHECKER_PATH, ...args], {
|
|
||||||
cwd: process.cwd(),
|
|
||||||
encoding: 'utf8',
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: result.status,
|
|
||||||
stdout: result.stdout || '',
|
|
||||||
stderr: result.stderr || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test('acyclic fixture passes runtime cycle check', () => {
|
|
||||||
const result = runRuntimeCycleCheck(path.join(FIXTURE_ROOT, 'acyclic'), true);
|
|
||||||
assert.equal(result.hasCycle, false);
|
|
||||||
assert.equal(result.fileCount >= 3, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cyclic fixture fails via CLI and prints readable cycle path', () => {
|
|
||||||
const result = runCheckerCli(['--strict', '--root', path.join(FIXTURE_ROOT, 'cyclic')]);
|
|
||||||
|
|
||||||
assert.equal(result.status, 1);
|
|
||||||
assert.match(result.stdout, /^\[FAIL\] runtime cycle check \(strict\)/m);
|
|
||||||
assert.match(result.stdout, /module-a\.ts/);
|
|
||||||
assert.match(result.stdout, /module-b\.ts|nested\/index\.ts/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('missing root returns actionable error', () => {
|
|
||||||
const missingRoot = path.join(FIXTURE_ROOT, 'does-not-exist');
|
|
||||||
const result = runCheckerCli(['--strict', '--root', missingRoot]);
|
|
||||||
|
|
||||||
assert.equal(result.status, 1);
|
|
||||||
assert.match(result.stdout, /Runtime root not found:/);
|
|
||||||
assert.match(result.stdout, /--root <path>/);
|
|
||||||
});
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
export type ParsedArgs = {
|
|
||||||
strict: boolean;
|
|
||||||
root: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RuntimeCycleCheckResult = {
|
|
||||||
root: string;
|
|
||||||
mode: 'warning' | 'strict';
|
|
||||||
fileCount: number;
|
|
||||||
hasCycle: boolean;
|
|
||||||
cyclePath: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_ROOT = path.join('src', 'main', 'runtime');
|
|
||||||
|
|
||||||
export function parseArgs(argv: string[]): ParsedArgs {
|
|
||||||
let strict = false;
|
|
||||||
let root = DEFAULT_ROOT;
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i];
|
|
||||||
if (arg === '--strict') {
|
|
||||||
strict = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--root') {
|
|
||||||
const raw = argv[i + 1];
|
|
||||||
if (!raw || raw.startsWith('--')) {
|
|
||||||
throw new Error('Missing value for --root. Usage: --root <path>');
|
|
||||||
}
|
|
||||||
root = raw;
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw new Error(`Unknown argument: ${arg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { strict, root };
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkTsFiles(root: string): string[] {
|
|
||||||
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
||||||
const files: string[] = [];
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = path.join(root, entry.name);
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
files.push(...walkTsFiles(fullPath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entry.isFile() && path.extname(entry.name) === '.ts') {
|
|
||||||
files.push(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractRelativeSpecifiers(source: string): string[] {
|
|
||||||
const specifiers = new Set<string>();
|
|
||||||
const pattern = /(?:import|export)\s+(?:[^'";]+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
||||||
for (const match of source.matchAll(pattern)) {
|
|
||||||
const specifier = match[1];
|
|
||||||
if (specifier.startsWith('.')) {
|
|
||||||
specifiers.add(specifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(specifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveRelativeTarget(importerFile: string, specifier: string): string | null {
|
|
||||||
const importerDir = path.dirname(importerFile);
|
|
||||||
const baseTarget = path.resolve(importerDir, specifier);
|
|
||||||
const extension = path.extname(baseTarget);
|
|
||||||
|
|
||||||
const candidates = extension
|
|
||||||
? [baseTarget]
|
|
||||||
: [`${baseTarget}.ts`, path.join(baseTarget, 'index.ts')];
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
||||||
return path.normalize(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toRelativePosix(root: string, filePath: string): string {
|
|
||||||
return path.relative(root, filePath).split(path.sep).join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function detectCycle(graph: Map<string, string[]>): string[] {
|
|
||||||
const state = new Map<string, 0 | 1 | 2>();
|
|
||||||
const stack: string[] = [];
|
|
||||||
|
|
||||||
const visit = (node: string): string[] => {
|
|
||||||
state.set(node, 1);
|
|
||||||
stack.push(node);
|
|
||||||
|
|
||||||
const neighbors = graph.get(node) ?? [];
|
|
||||||
for (const next of neighbors) {
|
|
||||||
const nextState = state.get(next) ?? 0;
|
|
||||||
if (nextState === 0) {
|
|
||||||
const cycle = visit(next);
|
|
||||||
if (cycle.length > 0) {
|
|
||||||
return cycle;
|
|
||||||
}
|
|
||||||
} else if (nextState === 1) {
|
|
||||||
const startIndex = stack.indexOf(next);
|
|
||||||
if (startIndex >= 0) {
|
|
||||||
return [...stack.slice(startIndex), next];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.pop();
|
|
||||||
state.set(node, 2);
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const nodes = Array.from(graph.keys()).sort();
|
|
||||||
for (const node of nodes) {
|
|
||||||
if ((state.get(node) ?? 0) !== 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const cycle = visit(node);
|
|
||||||
if (cycle.length > 0) {
|
|
||||||
return cycle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runRuntimeCycleCheck(rootArg: string, strict: boolean): RuntimeCycleCheckResult {
|
|
||||||
const root = path.resolve(process.cwd(), rootArg);
|
|
||||||
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
||||||
throw new Error(
|
|
||||||
`Runtime root not found: ${rootArg}. Use --root <path> to point at a directory.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = walkTsFiles(root)
|
|
||||||
.map((file) => path.normalize(file))
|
|
||||||
.sort();
|
|
||||||
const fileSet = new Set(files);
|
|
||||||
const graph = new Map<string, string[]>();
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const source = fs.readFileSync(file, 'utf8');
|
|
||||||
const specifiers = extractRelativeSpecifiers(source);
|
|
||||||
const edges = new Set<string>();
|
|
||||||
for (const specifier of specifiers) {
|
|
||||||
const target = resolveRelativeTarget(file, specifier);
|
|
||||||
if (!target) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (fileSet.has(target)) {
|
|
||||||
edges.add(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graph.set(file, Array.from(edges).sort());
|
|
||||||
}
|
|
||||||
|
|
||||||
const cycle = detectCycle(graph);
|
|
||||||
return {
|
|
||||||
root: rootArg,
|
|
||||||
mode: strict ? 'strict' : 'warning',
|
|
||||||
fileCount: files.length,
|
|
||||||
hasCycle: cycle.length > 0,
|
|
||||||
cyclePath: cycle.map((file) => toRelativePosix(root, file)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatSummary(result: RuntimeCycleCheckResult): string[] {
|
|
||||||
const lines: string[] = [];
|
|
||||||
if (!result.hasCycle) {
|
|
||||||
lines.push(
|
|
||||||
`[OK] runtime cycle check (${result.mode}) - ${result.fileCount} files scanned, no cycles detected`,
|
|
||||||
);
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = result.mode === 'strict' ? '[FAIL]' : '[WARN]';
|
|
||||||
lines.push(`${heading} runtime cycle check (${result.mode}) - cycle detected`);
|
|
||||||
lines.push(` root: ${result.root}`);
|
|
||||||
lines.push(` cycle: ${result.cyclePath.join(' -> ')}`);
|
|
||||||
lines.push(' Hint: break bidirectional imports by moving shared logic to leaf modules.');
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function main(argv: string[] = process.argv.slice(2)): void {
|
|
||||||
try {
|
|
||||||
const { strict, root } = parseArgs(argv);
|
|
||||||
const result = runRuntimeCycleCheck(root, strict);
|
|
||||||
for (const line of formatSummary(result)) {
|
|
||||||
console.log(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strict && result.hasCycle) {
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
console.log(`[FAIL] runtime cycle check (strict) - ${message}`);
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
main();
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import './utils';
|
|
||||||
export { value } from './feature';
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { utilValue } from './utils';
|
|
||||||
|
|
||||||
export const value = utilValue + 1;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const utilValue = 1;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { b } from './module-b';
|
|
||||||
|
|
||||||
export const a = b + 1;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { c as b } from './nested';
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { a } from '../module-a.ts';
|
|
||||||
|
|
||||||
export const c = a + 1;
|
|
||||||
Reference in New Issue
Block a user