@hublo/sentinel 0.1.0-alpha.10 → 0.1.0-alpha.11
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/dist/bin/sentinel.js +7 -2
- package/dist/bin/sentinel.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/sentinel.js
CHANGED
|
@@ -531,9 +531,14 @@ sentinel (${type}): ${asMessage(err)}
|
|
|
531
531
|
}
|
|
532
532
|
return worst;
|
|
533
533
|
}
|
|
534
|
-
|
|
534
|
+
async function main() {
|
|
535
|
+
const runSelectedVerb = verb === "update" ? runUpdate : runVerb;
|
|
536
|
+
const exitCode = await runSelectedVerb();
|
|
537
|
+
process.exit(exitCode);
|
|
538
|
+
}
|
|
539
|
+
main().catch((error) => {
|
|
535
540
|
process.stderr.write(`
|
|
536
|
-
sentinel: ${asMessage(
|
|
541
|
+
sentinel: ${asMessage(error)}
|
|
537
542
|
`);
|
|
538
543
|
process.exit(1);
|
|
539
544
|
});
|
package/dist/bin/sentinel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../bin/sentinel.ts","../../src/core/context.ts","../../src/core/discover-modules.ts","../../src/core/settings.ts","../../src/core/domain.ts","../../src/core/orchestrate.ts","../../src/core/render.ts","../../src/shared/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * sentinel CLI: a command composes verb × type × location.\n *\n * Exactly one verb (--run / --inspect / --report / --status / --update); a type (--typescript …)\n * or ALL when none is named; and a location resolved by `resolveContext` (a module\n * directory, or the workspace root with --module / --ci / all). The CLI is generic: it\n * resolves each target's adapter (honouring --runner) and dispatches; tools arrive as\n * adapters in later tickets. The flavour is detected from a module's dependencies\n * (deterministic, `node` default) and can be overridden with --flavour (recommended\n * for --update).\n */\nimport { program } from 'commander'\n\nimport { registerAdapters } from '../src/adapters.js'\nimport { resolveContext } from '../src/core/context.js'\nimport { FLAVOURS, TARGETS, VERBS, type Flavour, type Target } from '../src/core/domain.js'\nimport { dispatch } from '../src/core/dispatch.js'\nimport { analyse, generateSummaries } from '../src/core/orchestrate.js'\nimport { availableTargets } from '../src/core/registry.js'\nimport {\n renderStatusRow,\n renderStatusSummary,\n renderSummary,\n statusCoverage,\n} from '../src/core/render.js'\nimport { palette } from '../src/shared/color.js'\nimport { checkNodeVersion } from '../src/shared/node-version.js'\nimport { readOwnVersion } from '../src/shared/package-json.js'\n\n// Fail fast with a clear message on an unsupported Node (sentinel's coloured output needs\n// `styleText`'s stream option, Node 22.13+), instead of a cryptic crash deep in the CLI.\nconst nodeCheck = checkNodeVersion(process.versions.node)\nif (!nodeCheck.ok) {\n process.stderr.write(`${nodeCheck.message}\\n`)\n process.exit(1)\n}\n\n// Register every tool adapter up front, BEFORE parsing, so `--help` can reflect what is\n// actually wired (availableTargets) rather than a hardcoded list that pretends all targets\n// are ready. Registration has no dependency on the parsed options.\nregisterAdapters()\n\nprogram\n .name('sentinel')\n .description('One CLI that guards code health: presets, analysis, and arch checks.')\n .version(readOwnVersion())\n .configureHelp({ sortOptions: false })\n // On any parse error (unknown flag, missing value): suggest the closest flag and\n // point at --help, so a typo like `--lnt` gets \"did you mean --lint?\".\n .showSuggestionAfterError(true)\n .showHelpAfterError('(run \"sentinel --help\" for usage)')\n .addHelpText(\n 'before',\n [\n 'A check composes: verb + type + location.',\n ' verb what to do: --run --inspect --report --status --update',\n ' type which check: --lint --typescript ... (omit = all types; or --all)',\n ' where run from a MODULE dir → that module; from the ROOT → --module <name>,',\n ' --ci (affected), or all modules. --update targets one module only.',\n '',\n ].join('\\n'),\n )\n // verbs (pick one)\n .option('--run', 'execute the target tool')\n .option('--inspect', 'show the resolved configuration')\n .option('--update', 'generate/apply the config stubs')\n .option('--report', 'metrics and health report')\n .option('--status', 'adoption + conformity across modules (coverage + drift)')\n // targets (pick one, or --all)\n .option('--lint', 'linting')\n .option('--format', 'formatting')\n .option('--typescript', 'type checking')\n .option('--build', 'build')\n .option('--test', 'tests')\n .option('--static-analysis', 'cycles, complexity, duplication, centrality')\n .option('--runtime-analysis', 'bundle, Lighthouse, web vitals')\n .option('--arch', 'architecture boundaries')\n .option('--all', 'every target')\n // modifiers\n .option(\n '--module <name>',\n 'from the workspace root: scope to one module (omit = all; inside a module dir, drop this)',\n )\n .option('--flavour <name>', `override the detected stack preset (${FLAVOURS.join(', ')})`)\n .option('--runner <tool>', 'override the default runner (e.g. eslint, biome)')\n .option('--ci', 'CI mode: from the root, only the affected modules; non-zero exit on failure')\n .option('--fix', 'auto-fix where applicable')\n .option('--dry-run', 'preview the changes without writing (--update)')\n .option('--json', 'machine-readable JSON output (report/inspect/--dry-run)')\n .option(\n '--max-diagnostics <n>',\n 'cap the diagnostics embedded per module in --report (0 = no cap)',\n '100',\n )\n .addHelpText(\n 'after',\n [\n '',\n 'Examples:',\n ' sentinel --run --typescript # in a module → that module',\n ' sentinel --report --typescript --module bff-admin # from root → one module',\n ' sentinel --report # from root → all types, all modules',\n ' sentinel --report --ci # from root → affected only',\n ' sentinel --update --typescript --flavour react # write stubs for the current module',\n ].join('\\n'),\n )\n // Availability, derived from the registry so it never lies: only wired targets are\n // \"available now\"; the rest are honestly marked as planned.\n .addHelpText('after', () => {\n const available = availableTargets()\n const planned = TARGETS.filter((t) => !available.includes(t))\n return [\n '',\n `Available now: ${available.length ? available.map((t) => `--${t}`).join(', ') : '(none yet)'}`,\n planned.length\n ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(', ')}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .parse()\n\nconst opts = program.opts()\n\n// commander camelCases hyphenated flags (--static-analysis -> staticAnalysis).\nfunction toCamel(flag: string): string {\n return flag.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n}\n\n/** Exactly one of `keys` must be flagged; return it, else fail with guidance. */\nfunction pickOne<T extends string>(kind: string, keys: readonly T[]): T {\n const chosen = keys.filter((k) => opts[toCamel(k)])\n if (chosen.length !== 1) {\n const supported = keys.map((k) => `--${k}`).join(', ')\n program.error(\n chosen.length === 0\n ? `Missing a ${kind}. Supported: ${supported}.`\n : `Pick exactly one ${kind}: got ${chosen.map((k) => `--${k}`).join(', ')}.`,\n )\n }\n return chosen[0] as T\n}\n\n/**\n * The explicit `--flavour`, VALIDATED, or undefined when not given. When undefined,\n * the flavour is DETECTED from the module's dependencies (see `detect-framework.ts`);\n * this override is recommended for `--update`, where hoisted deps can make detection\n * fall back to `node`. An unknown value is a hard error (typo caught), not a silent guess.\n */\nfunction parseFlavour(value: unknown): Flavour | undefined {\n if (value === undefined) return undefined\n if (typeof value !== 'string' || !FLAVOURS.includes(value as Flavour)) {\n return program.error(\n `sentinel: unknown --flavour ${JSON.stringify(value)}. Supported: ${FLAVOURS.join(', ')}.`,\n )\n }\n return value as Flavour\n}\n\nfunction asMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nconst verb = pickOne('verb', VERBS)\nconst cwd = process.cwd()\nconst flavour = parseFlavour(opts.flavour)\n\n// Targets (the \"type\" axis): a named one, or ALL when none is given. Uniform for every\n// verb — run / inspect / report / update. `resolveContext` owns the \"location\" axis.\nconst namedTargets = TARGETS.filter((t) => opts[toCamel(t)])\nif (opts.all && namedTargets.length > 0) {\n program.error(\n `--all runs every target; drop the specific one(s): ${namedTargets.map((t) => `--${t}`).join(', ')}.`,\n )\n}\nconst targets: Target[] = opts.all || namedTargets.length === 0 ? [...TARGETS] : namedTargets\n// Whether the user named the target(s) themselves. When true, a target with no adapter is\n// a usage error (they asked for something sentinel cannot do). When false (`--all` / none),\n// an unsupported target is just an expected skip, shown but not failed.\nconst targetsExplicit = !(opts.all || namedTargets.length === 0)\n\n// --max-diagnostics: how many structured diagnostics --report embeds per module (0 = all).\n// `Number` (not parseInt) so a fractional value like `3.9` is rejected, not silently floored.\nconst maxDiagnostics = Number(opts.maxDiagnostics)\nif (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {\n program.error(\n `--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`,\n )\n}\n\n// --dry-run only previews a write.\nif (opts.dryRun && verb !== 'update') {\n program.error('--dry-run only applies to --update (the read verbs never write).')\n}\n\n/**\n * run / inspect / report: resolve the module context (cwd module, `--module`, all, or\n * `--ci` affected), then execute verb × targets across it and print.\n */\nasync function runVerb(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) })\n const out = palette(process.stdout)\n const err = palette(process.stderr)\n\n // `--run` streams the tool's own output to stdout, so a JSON envelope would be mixed in;\n // `--json` is only honoured for report/inspect. Say so instead of silently ignoring it.\n if (opts.json && verb === 'run') {\n process.stderr.write(\n err.warn(\n 'note: --json is ignored for --run (it streams the tool output); use --report --json for a machine envelope.',\n ) + '\\n',\n )\n }\n\n const started = Date.now()\n const { results, worstCode } = await analyse({\n verb: verb as 'run' | 'report' | 'inspect' | 'status',\n targets,\n modules,\n runner: opts.runner,\n flavour,\n targetsExplicit,\n maxDiagnostics,\n ci: Boolean(opts.ci),\n fix: Boolean(opts.fix),\n onProgress: (done, total, name) =>\n process.stderr.write(err.dim(` [${done}/${total}] ${name}\\n`)),\n })\n\n // Shape each outcome into a summary row, then print it colored. `run` rows carry no\n // metrics (bare head line); `status` has its own 3-state rows + a coverage footer;\n // `--json` bypasses the human rendering entirely.\n const summary = generateSummaries(results, targets)\n if (opts.json && verb !== 'run') {\n const payload =\n verb === 'status' ? { ...summary, coverage: statusCoverage(summary.results) } : summary\n process.stdout.write(JSON.stringify(payload, null, 2) + '\\n')\n } else if (verb === 'status') {\n for (const item of summary.results) process.stdout.write(renderStatusRow(item, out) + '\\n')\n process.stdout.write(renderStatusSummary(summary.results, out) + '\\n')\n } else {\n for (const item of summary.results) {\n process.stdout.write(renderSummary(item, out) + '\\n')\n }\n }\n process.stderr.write(\n err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms\\n`),\n )\n\n // An explicitly requested target with no adapter is a usage error: say so clearly and\n // exit non-zero, so a human or agent never mistakes \"nothing ran\" for a success.\n if (targetsExplicit && summary.skipped.length > 0) {\n const avail = availableTargets()\n process.stderr.write(\n err.fail(\n `sentinel: target(s) not available yet: ${summary.skipped.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${avail.length ? avail.map((t) => `--${t}`).join(', ') : '(none yet)'}.`,\n ) + '\\n',\n )\n return 1\n }\n\n // `run` always reports failures via its exit code; report/inspect only under --ci.\n return verb === 'run' || opts.ci ? worstCode : 0\n}\n\n/**\n * update: a WRITE. Same context rule, but it must resolve to exactly ONE module (the\n * current one, or `--module`). Adopting every module at once would be a big-bang, so\n * the implicit \"all\" is deliberately refused; adopt gradually.\n */\nasync function runUpdate(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: false })\n const [module] = modules\n if (scope === 'all' || scope === 'affected' || !module) {\n program.error(\n '--update writes files: target one module (run from its directory, or pass --module). ' +\n 'Adopting every module at once is intentionally not allowed — adopt gradually.',\n )\n }\n // Same policy as the read verbs: a target the user NAMED but that has no adapter is a\n // usage error; targets swept in by --all / no target are just skipped, never flooded as\n // \"No adapter…\" errors that would fail an otherwise-successful update.\n const available = availableTargets()\n if (targetsExplicit) {\n const unwired = targets.filter((type) => !available.includes(type))\n if (unwired.length > 0) {\n program.error(\n `target(s) not available yet: ${unwired.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${available.map((t) => `--${t}`).join(', ') || '(none yet)'}.`,\n )\n }\n }\n const toRun = targets.filter((type) => available.includes(type))\n\n let worst = 0\n for (const type of toRun) {\n try {\n const code = await dispatch({\n verb,\n target: type,\n runner: opts.runner,\n cwd: module.root,\n flavour,\n dryRun: Boolean(opts.dryRun),\n json: Boolean(opts.json),\n })\n worst = Math.max(worst, code)\n } catch (err) {\n // A genuine adapter failure (not an unwired target, those are filtered above).\n process.stderr.write(`\\nsentinel (${type}): ${asMessage(err)}\\n`)\n worst = Math.max(worst, 1)\n }\n }\n return worst\n}\n\n;(verb === 'update' ? runUpdate() : runVerb())\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n process.stderr.write(`\\nsentinel: ${asMessage(err)}\\n`)\n process.exit(1)\n })\n","/**\n * Context resolution: the composable \"location\" axis of a command.\n *\n * One rule for every read verb (run / inspect / report), so a developer works the way\n * they already work with nx, from their module or from the root:\n * - in a MODULE dir (has package.json/project.json, and is NOT the workspace root):\n * the context is THAT module; `--module` is redundant there and is rejected.\n * - at the workspace ROOT: `--module X` scopes to X, `--ci` scopes to the affected\n * set, and otherwise it is every module (the whole-repo status).\n * Update (a write) reuses this but forbids the implicit \"all\" (see the CLI).\n */\nimport { existsSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { readNxProjectName } from '../shared/package-json.js'\nimport { discoverModules, type ModuleRef } from './discover-modules.js'\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\n/** A directory is a module if it carries one of these (and is not the root). */\nconst MODULE_MARKERS = ['package.json', 'project.json'] as const\n\n/** How the context was resolved, for messages and the \"all\"-guard on update. */\nexport type ContextScope = 'cwd-module' | 'named-module' | 'affected' | 'all'\n\nexport interface ResolvedContext {\n modules: ModuleRef[]\n scope: ContextScope\n}\n\nfunction isModuleDir(cwd: string): boolean {\n return MODULE_MARKERS.some((marker) => existsSync(join(cwd, marker)))\n}\n\n/**\n * Resolve which modules a command targets from where it runs + its flags. Throws with\n * an actionable message when the combination is contradictory (e.g. `--module` from\n * inside a module) or the directory is neither a module nor the workspace root.\n */\nexport function resolveContext(\n cwd: string,\n opts: { module?: string; ci?: boolean },\n): ResolvedContext {\n const atRoot = existsSync(join(cwd, WORKSPACE_ROOT_MARKER))\n\n // In a module directory: the module IS the current one.\n if (!atRoot && isModuleDir(cwd)) {\n if (opts.module) {\n throw new Error(\n 'You are in a module directory: drop --module (the context is the current module).',\n )\n }\n if (opts.ci) {\n throw new Error('--ci selects the affected set from the workspace root; run it there.')\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return { modules: [{ name, root: cwd }], scope: 'cwd-module' }\n }\n\n // At the workspace root: --module (one), --ci (affected), or every module.\n if (atRoot) {\n if (opts.module) {\n const found = discoverModules(cwd).find((module) => module.name === opts.module)\n if (!found) throw new Error(`module \"${opts.module}\" not found in the workspace.`)\n return { modules: [found], scope: 'named-module' }\n }\n if (opts.ci) return { modules: discoverModules(cwd, { affected: true }), scope: 'affected' }\n return { modules: discoverModules(cwd), scope: 'all' }\n }\n\n throw new Error(\n `Run sentinel from a module directory or the workspace root ` +\n `(found neither ${MODULE_MARKERS.join('/')} nor ${WORKSPACE_ROOT_MARKER} here).`,\n )\n}\n","/**\n * Module discovery via nx. `--report`/`--inspect` without a `--module` analyse\n * every project; `--ci` narrows to the affected ones. We ask nx (its cached graph)\n * for the authoritative names + roots in a couple of calls, not one per project,\n * so it stays fast on the real monorepo.\n */\nimport { execFileSync } from 'node:child_process'\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { resolveBin } from '../shared/resolve-bin.js'\n\n/** A discovered module: its nx project name and absolute root. */\nexport interface ModuleRef {\n name: string\n root: string\n}\n\nfunction runNx(cwd: string, args: string[]): string {\n const nx = resolveBin(cwd, 'nx') ?? 'nx'\n try {\n // Disable the daemon for deterministic, CI-friendly one-shot invocations.\n return execFileSync(nx, args, {\n cwd,\n encoding: 'utf8',\n env: { ...process.env, NX_DAEMON: 'false' },\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `sentinel: could not run nx (${message}). Is nx installed in this workspace, and are you at its root?`,\n { cause: error },\n )\n }\n}\n\n/** Every module (name -> absolute root) from the nx project graph, in one call. */\nfunction readGraph(cwd: string): ModuleRef[] {\n const dir = mkdtempSync(join(tmpdir(), 'sentinel-nx-'))\n const file = join(dir, 'graph.json')\n try {\n runNx(cwd, ['graph', '--file', file])\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as {\n graph?: { nodes?: Record<string, { data: { root: string } }> }\n }\n const nodes = parsed.graph?.nodes\n if (!nodes || typeof nodes !== 'object') {\n throw new Error(\n 'sentinel: unexpected nx graph output (no graph.nodes); the installed nx version may be incompatible.',\n )\n }\n return Object.entries(nodes).map(([name, node]) => ({\n name,\n root: join(cwd, node.data.root),\n }))\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nexport function discoverModules(cwd: string, options: { affected?: boolean } = {}): ModuleRef[] {\n const modules = readGraph(cwd)\n if (!options.affected) return modules\n const affected = new Set(\n JSON.parse(runNx(cwd, ['show', 'projects', '--affected', '--json'])) as string[],\n )\n return modules.filter((module) => affected.has(module.name))\n}\n","/**\n * Tunable settings: the knobs you would actually change. The fixed vocabulary and\n * its types live in `domain.ts`.\n *\n * Note: the flavour is DETECTED from a module's dependencies (see\n * `detect-framework.ts`), deterministically, `node` when no framework dep is present.\n * `--flavour` overrides the detection (recommended for `--update`, since hoisted deps\n * can make detection return `node`). The detection table lives in `detect-framework.ts`.\n */\n\n/** A file that marks a workspace root; sentinel refuses to operate there. */\nexport const WORKSPACE_ROOT_MARKER = 'nx.json'\n","/**\n * The domain vocabulary: the fixed sets of verbs, targets, and flavours, and the\n * types derived from them. This is the model, and the extension point: adding a\n * verb / target / flavour is a one-line edit to a list here, and because the types\n * are DERIVED (`(typeof LIST)[number]`), the compiler forces every switch/handler\n * to cover the new member.\n *\n * Tunable behaviour (defaults, detection signals, marker filenames) lives in\n * `settings.ts`, not here.\n */\n\n/** Verbs: what to do. Each maps to an adapter method in dispatch. */\nexport const VERBS = ['run', 'inspect', 'update', 'report', 'status'] as const\nexport type Verb = (typeof VERBS)[number]\n\n/** Targets: the kind of check. The CLI `--<target>` flags map 1:1 to these. */\nexport const TARGETS = [\n 'lint',\n 'format',\n 'typescript',\n 'build',\n 'test',\n 'static-analysis',\n 'runtime-analysis',\n 'arch',\n] as const\nexport type Target = (typeof TARGETS)[number]\n\n/** Flavours: the stack preset a project resolves to (strict by default). */\nexport const FLAVOURS = ['react', 'nest', 'svelte', 'node'] as const\nexport type Flavour = (typeof FLAVOURS)[number]\n\n/**\n * The outcome of one module × target check. `unsupported` is a distinct third state\n * (a requested target has no adapter yet): it must never read as a pass, and an\n * explicitly-requested unsupported target is a usage error (non-zero exit), while an\n * unsupported target swept in by `--all` is just skipped and shown.\n */\nexport type ResultStatus = 'ok' | 'failed' | 'unsupported'\n","/**\n * Orchestration for `--report`/`--inspect`: analyse a set of modules across a set\n * of targets, then shape the outcomes into one versioned, parseable summary.\n *\n * The engine resolves each module's flavour and adapter and calls the per-module\n * method; a target with no adapter yet is simply skipped. `generateSummaries`\n * builds the aggregate by reusing `generateSummary` for each result (one factory,\n * no duplicated shaping between the single- and multi-module paths).\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { ModuleRef } from './discover-modules.js'\nimport type { Flavour, ResultStatus, Target } from './domain.js'\nimport { availableTargets, resolve } from './registry.js'\nimport type { RunContext } from './types.js'\n\n/** One project × target outcome. */\nexport interface AnalyseResult {\n project: string\n target: Target\n flavour: Flavour\n ok: boolean\n /**\n * The three-state outcome. `ok` stays for back-compat (`status === 'ok'`), but\n * `status` is the source of truth: `unsupported` (no adapter yet) must never read\n * as a pass.\n */\n status: ResultStatus\n /** `--report`: metrics (errors, implicit-any); `--inspect`: the resolved config. */\n data: unknown\n}\n\nexport interface AnalyseParams {\n verb: 'run' | 'report' | 'inspect' | 'status'\n targets: readonly Target[]\n modules: readonly ModuleRef[]\n runner?: string\n /** Explicit `--flavour`, when declared; otherwise detected per module from deps. */\n flavour?: Flavour\n /**\n * Whether the targets were named by the user (vs swept in by `--all` / no target). When\n * true, a target with no adapter yields a visible per-module `unsupported` row; when\n * false, it is skipped silently per module (the envelope's `skipped` still lists it once,\n * so `--all` does not flood the output with N×M unsupported rows).\n */\n targetsExplicit: boolean\n ci: boolean\n fix: boolean\n /** Cap on structured diagnostics per module in `--report` (0 = no cap). */\n maxDiagnostics?: number\n /** Called after each module, for progress display. */\n onProgress?: (done: number, total: number, moduleName: string) => void\n}\n\n/** Run the analyse across modules × targets. Returns the results + worst exit code. */\nexport async function analyse(\n params: AnalyseParams,\n): Promise<{ results: AnalyseResult[]; worstCode: number }> {\n const results: AnalyseResult[] = []\n let worstCode = 0\n let done = 0\n\n for (const module of params.modules) {\n // Explicit --flavour wins; otherwise detect it from the module's deps and, when the\n // module looks mixed (e.g. a Sails backend that also pulls React), warn and point at\n // --flavour rather than choosing silently.\n let flavour: Flavour\n if (params.flavour) {\n flavour = params.flavour\n } else {\n const detection = describeFramework(readProjectPackageJson(module.root))\n flavour = detection.flavour\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: ${module.name}: flavour is ambiguous, detected \"${flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to be explicit.`,\n ) + '\\n',\n )\n }\n }\n const ctx: RunContext = {\n module: module.name,\n cwd: module.root,\n flavour,\n ci: params.ci,\n fix: params.fix,\n maxDiagnostics: params.maxDiagnostics,\n }\n for (const target of params.targets) {\n let adapter\n try {\n adapter = resolve(target, flavour, params.runner)\n } catch (error) {\n // No adapter for this target yet. When the user named it, emit a visible per-module\n // `unsupported` row so it never reads as a pass. When it was swept in by `--all`,\n // skip silently per module: the envelope's `skipped` still lists it once, so `--all`\n // does not flood the output with one unsupported row per module × unwired target.\n if (params.targetsExplicit) {\n const reason = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'unsupported',\n data: { reason },\n })\n }\n continue\n }\n // Isolate every check: one module/target that throws must not abort the whole\n // sweep. A failure becomes a `ok:false` row (with the error) so `--report --all`\n // still returns a complete picture across a large workspace.\n try {\n if (params.verb === 'run') {\n // Label the run so multi-module output (root/--all) is readable; the tool\n // streams its own output (stdio inherit) between headers.\n if (params.modules.length > 1) {\n const err = palette(process.stderr)\n process.stderr.write(\n `\\n ${err.accent('▶')} ${err.strong(module.name)} ${err.dim(`(${flavour})`)} ${target}\\n`,\n )\n }\n const result = await adapter.run(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'report') {\n const result = await adapter.report(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: result.metrics ?? {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'inspect') {\n const config = await adapter.inspect(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: true,\n status: 'ok',\n data: config,\n })\n } else {\n // status: adoption + conformity, read from the committed config (no tool run).\n // A drifted adopted module is a conformance failure; a non-adopted one is not.\n const status = await adapter.status(ctx)\n const ok = !status.adopted || status.conformant\n results.push({\n project: module.name,\n target,\n flavour,\n ok,\n status: ok ? 'ok' : 'failed',\n data: status,\n })\n if (!ok) worstCode = Math.max(worstCode, 1)\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'failed',\n data: { error: message },\n })\n worstCode = Math.max(worstCode, 1)\n }\n }\n params.onProgress?.((done += 1), params.modules.length, module.name)\n }\n\n return { results, worstCode }\n}\n\n/** The shaped summary for ONE result (the factory unit). */\nexport function generateSummary(result: AnalyseResult): Record<string, unknown> {\n const { project, target, flavour, ok, status, data } = result\n const details =\n data && typeof data === 'object' ? (data as Record<string, unknown>) : { value: data }\n return { project, target, flavour, ok, status, ...details }\n}\n\n/**\n * The aggregate, versioned envelope for MANY results, built from `generateSummary`.\n * `executed` / `skipped` split the REQUESTED targets by whether an adapter is wired, so an\n * agent sees what actually ran vs what was unsupported at the top level without scanning\n * every row (and independently of `--all` suppressing per-module unsupported rows).\n * schemaVersion is 2 since these fields are new to the shape.\n */\nexport function generateSummaries(\n results: readonly AnalyseResult[],\n requestedTargets: readonly Target[],\n): {\n schemaVersion: number\n executed: Target[]\n skipped: Target[]\n results: Record<string, unknown>[]\n} {\n const available = new Set(availableTargets())\n return {\n schemaVersion: 2,\n executed: requestedTargets.filter((t) => available.has(t)),\n skipped: requestedTargets.filter((t) => !available.has(t)),\n results: results.map(generateSummary),\n }\n}\n","/**\n * Human rendering of one summary row (`--run`/`--report`/`--inspect`), colored via a\n * `Palette`. Pure and stream-agnostic: it returns the text, the CLI writes it. `--json`\n * bypasses this entirely (machines get the raw envelope).\n *\n * The goal is legibility: the pass/fail mark and the module name read at a glance, the\n * error counts stand out when non-zero, and the phased `deferred` rules become their own\n * indented block instead of an inline JSON blob.\n */\nimport type { Palette } from '../shared/color.js'\n\n/** Keys carried by the row head (or redundant with it), never repeated in the details. */\nconst HEAD_KEYS = new Set([\n 'project',\n 'module',\n 'target',\n 'flavour',\n 'ok',\n 'status',\n 'reason',\n // The structured diagnostics are for `--json` consumers; the human row stays the concise\n // `errors=N` line rather than dumping every diagnostic inline.\n 'diagnostics',\n 'diagnosticsTruncated',\n])\n\n/** A deferred (phased) rule, as `--inspect` reports it. */\ninterface DeferredRule {\n rule: string\n phase: number\n reason: string\n}\n\nfunction isDeferredRules(value: unknown): value is DeferredRule[] {\n return (\n Array.isArray(value) &&\n value.every((entry) => typeof entry === 'object' && entry !== null && 'rule' in entry)\n )\n}\n\n/** Color a detail value: error counts are the signal, so red when >0, green at 0. */\nfunction renderValue(key: string, value: unknown, p: Palette): string {\n if ((key === 'errors' || key === 'implicitAny') && typeof value === 'number') {\n return value > 0 ? p.fail(String(value)) : p.ok(String(value))\n }\n // implicitAny is `deferred` when the rule is off (phase 1): honest, not a fake 0.\n if (key === 'implicitAny' && value === 'deferred') return p.warn('deferred')\n return typeof value === 'string' ? value : JSON.stringify(value)\n}\n\n/**\n * `unsupported` is a third state (a requested target with no adapter yet): a distinct dim\n * marker, never a ✓ (would read as a pass) nor a ✗ (would read as a failure of the code).\n * Shared by every row renderer (summary AND status) so the state can never render as a\n * legitimate outcome on one path and not the other.\n */\nfunction renderUnsupportedRow(item: Record<string, unknown>, p: Palette): string {\n const reason = typeof item.reason === 'string' ? ` ${p.dim(`(${item.reason})`)}` : ''\n const head = ` ${p.dim('·')} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n return `${head} ${p.dim('—')} ${p.warn('unsupported')}${reason}`\n}\n\n/**\n * One summary row → colored lines: a head line (`✓ name (flavour) target — key=value …`)\n * plus, when present, an indented `deferred` block listing the phased rules.\n */\nexport function renderSummary(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const ok = item.ok === true\n const mark = ok ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n // Split details: the phased `deferred` rules get their own block; everything else is\n // rendered inline as dim `key=value` pairs (the head keys are dropped as redundant).\n const entries = Object.entries(item).filter(([key]) => !HEAD_KEYS.has(key))\n const deferred = entries.find(([key]) => key === 'deferred')?.[1]\n const inline = entries\n .filter(([key]) => key !== 'deferred')\n .map(([key, value]) => `${p.dim(`${key}=`)}${renderValue(key, value, p)}`)\n .join(' ')\n\n const lines = [inline ? `${head} ${p.dim('—')} ${inline}` : head]\n\n if (isDeferredRules(deferred) && deferred.length > 0) {\n lines.push(` ${p.warn('deferred (phase 1, non-breaking):')}`)\n for (const { rule, phase, reason } of deferred) {\n lines.push(` ${p.warn('•')} ${p.strong(rule)} ${p.dim(`— phase ${phase}: ${reason}`)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Workspace adoption + conformity totals, for the `--status` footer / JSON. */\nexport interface Coverage {\n total: number\n adopted: number\n conformant: number\n drifted: number\n}\n\nexport function statusCoverage(items: readonly Record<string, unknown>[]): Coverage {\n // Only real status rows count toward coverage; an `unsupported` target (a requested\n // target with no adapter) must not inflate the denominator (`total`).\n const relevant = items.filter((i) => i.status !== 'unsupported')\n const adopted = relevant.filter((i) => i.adopted === true)\n const conformant = adopted.filter((i) => i.conformant === true)\n return {\n total: relevant.length,\n adopted: adopted.length,\n conformant: conformant.length,\n drifted: adopted.length - conformant.length,\n }\n}\n\n/** Short preset name for display (`@hublo/sentinel/tsconfig/nest` -> `nest`). */\nfunction presetShort(preset: unknown): string {\n return typeof preset === 'string' ? preset.replace('@hublo/sentinel/tsconfig/', '') : '?'\n}\n\n/**\n * One `--status` row, three states: not-adopted (dim `·`), adopted + conformant (green\n * `✓`), adopted + drifted (red `✗`, with the drifted keys named).\n */\nexport function renderStatusRow(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const adopted = item.adopted === true\n const conformant = item.conformant === true\n const mark = !adopted ? p.dim('·') : conformant ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n if (!adopted) return `${head} ${p.dim('— not adopted')}`\n if (conformant) {\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)}) conformant`)}`\n }\n const drift = Array.isArray(item.drift) ? item.drift.join(', ') : ''\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)})`)} ${p.fail(`drift: ${drift}`)}`\n}\n\n/** The `--status` footer: `coverage: X/N adopted · K/X conformant`. */\nexport function renderStatusSummary(items: readonly Record<string, unknown>[], p: Palette): string {\n const c = statusCoverage(items)\n const drift = c.drifted > 0 ? p.fail(` · ${c.drifted} drifted`) : ''\n return ` ${p.strong('coverage:')} ${c.adopted}/${c.total} adopted ${p.dim('·')} ${c.conformant}/${c.adopted} conformant${drift}`\n}\n","/**\n * Node version guard. sentinel colours output with `util.styleText`, added in Node 20.12, so\n * an older runtime (the tester ran serviceapp on Node 10) crashes cryptically. We check up\n * front and fail with a clear, actionable message instead. Kept as a pure function so it is\n * unit-testable without spawning Node. The `{ stream }` option we pass is newer (22.13) but\n * older 20.x ignore it gracefully, so 20.12 is the real floor (and matches CI on Node 20).\n */\n\n/** The minimum Node this CLI supports (the `util.styleText` floor). */\nexport const MIN_NODE = '20.12.0'\n\n/** Parse `24.15.0` (or `v24.15.0`) into `[major, minor, patch]`; missing parts are 0. */\nfunction parts(version: string): [number, number, number] {\n const [major = 0, minor = 0, patch = 0] = version\n .replace(/^v/, '')\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n return [major, minor, patch]\n}\n\n/**\n * Whether `current` (e.g. `process.versions.node`) meets `min`. Compares major, then minor,\n * then patch. Returns `{ ok }` and, when not, a clear message telling the user their version,\n * the required one, and how to switch.\n */\nexport function checkNodeVersion(\n current: string,\n min: string = MIN_NODE,\n): { ok: boolean; message?: string } {\n const [cMajor, cMinor, cPatch] = parts(current)\n const [mMajor, mMinor, mPatch] = parts(min)\n const ok =\n cMajor > mMajor ||\n (cMajor === mMajor && cMinor > mMinor) ||\n (cMajor === mMajor && cMinor === mMinor && cPatch >= mPatch)\n if (ok) return { ok: true }\n return {\n ok: false,\n message:\n `sentinel requires Node >= ${min}, but you are on ${current}. ` +\n `Switch with fnm/nvm (e.g. \\`fnm use ${mMajor}\\`) and re-run.`,\n }\n}\n"],"mappings":"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","names":["join","cwd","cwd","join","opts","flavour"]}
|
|
1
|
+
{"version":3,"sources":["../../bin/sentinel.ts","../../src/core/context.ts","../../src/core/discover-modules.ts","../../src/core/settings.ts","../../src/core/domain.ts","../../src/core/orchestrate.ts","../../src/core/render.ts","../../src/shared/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * sentinel CLI: a command composes verb × type × location.\n *\n * Exactly one verb (--run / --inspect / --report / --status / --update); a type (--typescript …)\n * or ALL when none is named; and a location resolved by `resolveContext` (a module\n * directory, or the workspace root with --module / --ci / all). The CLI is generic: it\n * resolves each target's adapter (honouring --runner) and dispatches; tools arrive as\n * adapters in later tickets. The flavour is detected from a module's dependencies\n * (deterministic, `node` default) and can be overridden with --flavour (recommended\n * for --update).\n */\nimport { program } from 'commander'\n\nimport { registerAdapters } from '../src/adapters.js'\nimport { resolveContext } from '../src/core/context.js'\nimport { FLAVOURS, TARGETS, VERBS, type Flavour, type Target } from '../src/core/domain.js'\nimport { dispatch } from '../src/core/dispatch.js'\nimport { analyse, generateSummaries } from '../src/core/orchestrate.js'\nimport { availableTargets } from '../src/core/registry.js'\nimport {\n renderStatusRow,\n renderStatusSummary,\n renderSummary,\n statusCoverage,\n} from '../src/core/render.js'\nimport { palette } from '../src/shared/color.js'\nimport { checkNodeVersion } from '../src/shared/node-version.js'\nimport { readOwnVersion } from '../src/shared/package-json.js'\n\n// Fail fast with a clear message on an unsupported Node (sentinel's coloured output needs\n// `styleText`'s stream option, Node 22.13+), instead of a cryptic crash deep in the CLI.\nconst nodeCheck = checkNodeVersion(process.versions.node)\nif (!nodeCheck.ok) {\n process.stderr.write(`${nodeCheck.message}\\n`)\n process.exit(1)\n}\n\n// Register every tool adapter up front, BEFORE parsing, so `--help` can reflect what is\n// actually wired (availableTargets) rather than a hardcoded list that pretends all targets\n// are ready. Registration has no dependency on the parsed options.\nregisterAdapters()\n\nprogram\n .name('sentinel')\n .description('One CLI that guards code health: presets, analysis, and arch checks.')\n .version(readOwnVersion())\n .configureHelp({ sortOptions: false })\n // On any parse error (unknown flag, missing value): suggest the closest flag and\n // point at --help, so a typo like `--lnt` gets \"did you mean --lint?\".\n .showSuggestionAfterError(true)\n .showHelpAfterError('(run \"sentinel --help\" for usage)')\n .addHelpText(\n 'before',\n [\n 'A check composes: verb + type + location.',\n ' verb what to do: --run --inspect --report --status --update',\n ' type which check: --lint --typescript ... (omit = all types; or --all)',\n ' where run from a MODULE dir → that module; from the ROOT → --module <name>,',\n ' --ci (affected), or all modules. --update targets one module only.',\n '',\n ].join('\\n'),\n )\n // verbs (pick one)\n .option('--run', 'execute the target tool')\n .option('--inspect', 'show the resolved configuration')\n .option('--update', 'generate/apply the config stubs')\n .option('--report', 'metrics and health report')\n .option('--status', 'adoption + conformity across modules (coverage + drift)')\n // targets (pick one, or --all)\n .option('--lint', 'linting')\n .option('--format', 'formatting')\n .option('--typescript', 'type checking')\n .option('--build', 'build')\n .option('--test', 'tests')\n .option('--static-analysis', 'cycles, complexity, duplication, centrality')\n .option('--runtime-analysis', 'bundle, Lighthouse, web vitals')\n .option('--arch', 'architecture boundaries')\n .option('--all', 'every target')\n // modifiers\n .option(\n '--module <name>',\n 'from the workspace root: scope to one module (omit = all; inside a module dir, drop this)',\n )\n .option('--flavour <name>', `override the detected stack preset (${FLAVOURS.join(', ')})`)\n .option('--runner <tool>', 'override the default runner (e.g. eslint, biome)')\n .option('--ci', 'CI mode: from the root, only the affected modules; non-zero exit on failure')\n .option('--fix', 'auto-fix where applicable')\n .option('--dry-run', 'preview the changes without writing (--update)')\n .option('--json', 'machine-readable JSON output (report/inspect/--dry-run)')\n .option(\n '--max-diagnostics <n>',\n 'cap the diagnostics embedded per module in --report (0 = no cap)',\n '100',\n )\n .addHelpText(\n 'after',\n [\n '',\n 'Examples:',\n ' sentinel --run --typescript # in a module → that module',\n ' sentinel --report --typescript --module bff-admin # from root → one module',\n ' sentinel --report # from root → all types, all modules',\n ' sentinel --report --ci # from root → affected only',\n ' sentinel --update --typescript --flavour react # write stubs for the current module',\n ].join('\\n'),\n )\n // Availability, derived from the registry so it never lies: only wired targets are\n // \"available now\"; the rest are honestly marked as planned.\n .addHelpText('after', () => {\n const available = availableTargets()\n const planned = TARGETS.filter((t) => !available.includes(t))\n return [\n '',\n `Available now: ${available.length ? available.map((t) => `--${t}`).join(', ') : '(none yet)'}`,\n planned.length\n ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(', ')}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .parse()\n\nconst opts = program.opts()\n\n// commander camelCases hyphenated flags (--static-analysis -> staticAnalysis).\nfunction toCamel(flag: string): string {\n return flag.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n}\n\n/** Exactly one of `keys` must be flagged; return it, else fail with guidance. */\nfunction pickOne<T extends string>(kind: string, keys: readonly T[]): T {\n const chosen = keys.filter((k) => opts[toCamel(k)])\n if (chosen.length !== 1) {\n const supported = keys.map((k) => `--${k}`).join(', ')\n program.error(\n chosen.length === 0\n ? `Missing a ${kind}. Supported: ${supported}.`\n : `Pick exactly one ${kind}: got ${chosen.map((k) => `--${k}`).join(', ')}.`,\n )\n }\n return chosen[0] as T\n}\n\n/**\n * The explicit `--flavour`, VALIDATED, or undefined when not given. When undefined,\n * the flavour is DETECTED from the module's dependencies (see `detect-framework.ts`);\n * this override is recommended for `--update`, where hoisted deps can make detection\n * fall back to `node`. An unknown value is a hard error (typo caught), not a silent guess.\n */\nfunction parseFlavour(value: unknown): Flavour | undefined {\n if (value === undefined) return undefined\n if (typeof value !== 'string' || !FLAVOURS.includes(value as Flavour)) {\n return program.error(\n `sentinel: unknown --flavour ${JSON.stringify(value)}. Supported: ${FLAVOURS.join(', ')}.`,\n )\n }\n return value as Flavour\n}\n\nfunction asMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nconst verb = pickOne('verb', VERBS)\nconst cwd = process.cwd()\nconst flavour = parseFlavour(opts.flavour)\n\n// Targets (the \"type\" axis): a named one, or ALL when none is given. Uniform for every\n// verb — run / inspect / report / update. `resolveContext` owns the \"location\" axis.\nconst namedTargets = TARGETS.filter((t) => opts[toCamel(t)])\nif (opts.all && namedTargets.length > 0) {\n program.error(\n `--all runs every target; drop the specific one(s): ${namedTargets.map((t) => `--${t}`).join(', ')}.`,\n )\n}\nconst targets: Target[] = opts.all || namedTargets.length === 0 ? [...TARGETS] : namedTargets\n// Whether the user named the target(s) themselves. When true, a target with no adapter is\n// a usage error (they asked for something sentinel cannot do). When false (`--all` / none),\n// an unsupported target is just an expected skip, shown but not failed.\nconst targetsExplicit = !(opts.all || namedTargets.length === 0)\n\n// --max-diagnostics: how many structured diagnostics --report embeds per module (0 = all).\n// `Number` (not parseInt) so a fractional value like `3.9` is rejected, not silently floored.\nconst maxDiagnostics = Number(opts.maxDiagnostics)\nif (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {\n program.error(\n `--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`,\n )\n}\n\n// --dry-run only previews a write.\nif (opts.dryRun && verb !== 'update') {\n program.error('--dry-run only applies to --update (the read verbs never write).')\n}\n\n/**\n * run / inspect / report: resolve the module context (cwd module, `--module`, all, or\n * `--ci` affected), then execute verb × targets across it and print.\n */\nasync function runVerb(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) })\n const out = palette(process.stdout)\n const err = palette(process.stderr)\n\n // `--run` streams the tool's own output to stdout, so a JSON envelope would be mixed in;\n // `--json` is only honoured for report/inspect. Say so instead of silently ignoring it.\n if (opts.json && verb === 'run') {\n process.stderr.write(\n err.warn(\n 'note: --json is ignored for --run (it streams the tool output); use --report --json for a machine envelope.',\n ) + '\\n',\n )\n }\n\n const started = Date.now()\n const { results, worstCode } = await analyse({\n verb: verb as 'run' | 'report' | 'inspect' | 'status',\n targets,\n modules,\n runner: opts.runner,\n flavour,\n targetsExplicit,\n maxDiagnostics,\n ci: Boolean(opts.ci),\n fix: Boolean(opts.fix),\n onProgress: (done, total, name) =>\n process.stderr.write(err.dim(` [${done}/${total}] ${name}\\n`)),\n })\n\n // Shape each outcome into a summary row, then print it colored. `run` rows carry no\n // metrics (bare head line); `status` has its own 3-state rows + a coverage footer;\n // `--json` bypasses the human rendering entirely.\n const summary = generateSummaries(results, targets)\n if (opts.json && verb !== 'run') {\n const payload =\n verb === 'status' ? { ...summary, coverage: statusCoverage(summary.results) } : summary\n process.stdout.write(JSON.stringify(payload, null, 2) + '\\n')\n } else if (verb === 'status') {\n for (const item of summary.results) process.stdout.write(renderStatusRow(item, out) + '\\n')\n process.stdout.write(renderStatusSummary(summary.results, out) + '\\n')\n } else {\n for (const item of summary.results) {\n process.stdout.write(renderSummary(item, out) + '\\n')\n }\n }\n process.stderr.write(\n err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms\\n`),\n )\n\n // An explicitly requested target with no adapter is a usage error: say so clearly and\n // exit non-zero, so a human or agent never mistakes \"nothing ran\" for a success.\n if (targetsExplicit && summary.skipped.length > 0) {\n const avail = availableTargets()\n process.stderr.write(\n err.fail(\n `sentinel: target(s) not available yet: ${summary.skipped.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${avail.length ? avail.map((t) => `--${t}`).join(', ') : '(none yet)'}.`,\n ) + '\\n',\n )\n return 1\n }\n\n // `run` always reports failures via its exit code; report/inspect only under --ci.\n return verb === 'run' || opts.ci ? worstCode : 0\n}\n\n/**\n * update: a WRITE. Same context rule, but it must resolve to exactly ONE module (the\n * current one, or `--module`). Adopting every module at once would be a big-bang, so\n * the implicit \"all\" is deliberately refused; adopt gradually.\n */\nasync function runUpdate(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: false })\n const [module] = modules\n if (scope === 'all' || scope === 'affected' || !module) {\n program.error(\n '--update writes files: target one module (run from its directory, or pass --module). ' +\n 'Adopting every module at once is intentionally not allowed — adopt gradually.',\n )\n }\n // Same policy as the read verbs: a target the user NAMED but that has no adapter is a\n // usage error; targets swept in by --all / no target are just skipped, never flooded as\n // \"No adapter…\" errors that would fail an otherwise-successful update.\n const available = availableTargets()\n if (targetsExplicit) {\n const unwired = targets.filter((type) => !available.includes(type))\n if (unwired.length > 0) {\n program.error(\n `target(s) not available yet: ${unwired.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${available.map((t) => `--${t}`).join(', ') || '(none yet)'}.`,\n )\n }\n }\n const toRun = targets.filter((type) => available.includes(type))\n\n let worst = 0\n for (const type of toRun) {\n try {\n const code = await dispatch({\n verb,\n target: type,\n runner: opts.runner,\n cwd: module.root,\n flavour,\n dryRun: Boolean(opts.dryRun),\n json: Boolean(opts.json),\n })\n worst = Math.max(worst, code)\n } catch (err) {\n // A genuine adapter failure (not an unwired target, those are filtered above).\n process.stderr.write(`\\nsentinel (${type}): ${asMessage(err)}\\n`)\n worst = Math.max(worst, 1)\n }\n }\n return worst\n}\n\n/**\n * Entry point. `--update` writes the config stubs; the read verbs\n * (run / inspect / report / status) resolve a location and execute.\n * Exit with the command's own status code, or 1 on an unexpected error.\n */\nasync function main(): Promise<void> {\n const runSelectedVerb = verb === 'update' ? runUpdate : runVerb\n const exitCode = await runSelectedVerb()\n process.exit(exitCode)\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`\\nsentinel: ${asMessage(error)}\\n`)\n process.exit(1)\n})\n","/**\n * Context resolution: the composable \"location\" axis of a command.\n *\n * One rule for every read verb (run / inspect / report), so a developer works the way\n * they already work with nx, from their module or from the root:\n * - in a MODULE dir (has package.json/project.json, and is NOT the workspace root):\n * the context is THAT module; `--module` is redundant there and is rejected.\n * - at the workspace ROOT: `--module X` scopes to X, `--ci` scopes to the affected\n * set, and otherwise it is every module (the whole-repo status).\n * Update (a write) reuses this but forbids the implicit \"all\" (see the CLI).\n */\nimport { existsSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { readNxProjectName } from '../shared/package-json.js'\nimport { discoverModules, type ModuleRef } from './discover-modules.js'\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\n/** A directory is a module if it carries one of these (and is not the root). */\nconst MODULE_MARKERS = ['package.json', 'project.json'] as const\n\n/** How the context was resolved, for messages and the \"all\"-guard on update. */\nexport type ContextScope = 'cwd-module' | 'named-module' | 'affected' | 'all'\n\nexport interface ResolvedContext {\n modules: ModuleRef[]\n scope: ContextScope\n}\n\nfunction isModuleDir(cwd: string): boolean {\n return MODULE_MARKERS.some((marker) => existsSync(join(cwd, marker)))\n}\n\n/**\n * Resolve which modules a command targets from where it runs + its flags. Throws with\n * an actionable message when the combination is contradictory (e.g. `--module` from\n * inside a module) or the directory is neither a module nor the workspace root.\n */\nexport function resolveContext(\n cwd: string,\n opts: { module?: string; ci?: boolean },\n): ResolvedContext {\n const atRoot = existsSync(join(cwd, WORKSPACE_ROOT_MARKER))\n\n // In a module directory: the module IS the current one.\n if (!atRoot && isModuleDir(cwd)) {\n if (opts.module) {\n throw new Error(\n 'You are in a module directory: drop --module (the context is the current module).',\n )\n }\n if (opts.ci) {\n throw new Error('--ci selects the affected set from the workspace root; run it there.')\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return { modules: [{ name, root: cwd }], scope: 'cwd-module' }\n }\n\n // At the workspace root: --module (one), --ci (affected), or every module.\n if (atRoot) {\n if (opts.module) {\n const found = discoverModules(cwd).find((module) => module.name === opts.module)\n if (!found) throw new Error(`module \"${opts.module}\" not found in the workspace.`)\n return { modules: [found], scope: 'named-module' }\n }\n if (opts.ci) return { modules: discoverModules(cwd, { affected: true }), scope: 'affected' }\n return { modules: discoverModules(cwd), scope: 'all' }\n }\n\n throw new Error(\n `Run sentinel from a module directory or the workspace root ` +\n `(found neither ${MODULE_MARKERS.join('/')} nor ${WORKSPACE_ROOT_MARKER} here).`,\n )\n}\n","/**\n * Module discovery via nx. `--report`/`--inspect` without a `--module` analyse\n * every project; `--ci` narrows to the affected ones. We ask nx (its cached graph)\n * for the authoritative names + roots in a couple of calls, not one per project,\n * so it stays fast on the real monorepo.\n */\nimport { execFileSync } from 'node:child_process'\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { resolveBin } from '../shared/resolve-bin.js'\n\n/** A discovered module: its nx project name and absolute root. */\nexport interface ModuleRef {\n name: string\n root: string\n}\n\nfunction runNx(cwd: string, args: string[]): string {\n const nx = resolveBin(cwd, 'nx') ?? 'nx'\n try {\n // Disable the daemon for deterministic, CI-friendly one-shot invocations.\n return execFileSync(nx, args, {\n cwd,\n encoding: 'utf8',\n env: { ...process.env, NX_DAEMON: 'false' },\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `sentinel: could not run nx (${message}). Is nx installed in this workspace, and are you at its root?`,\n { cause: error },\n )\n }\n}\n\n/** Every module (name -> absolute root) from the nx project graph, in one call. */\nfunction readGraph(cwd: string): ModuleRef[] {\n const dir = mkdtempSync(join(tmpdir(), 'sentinel-nx-'))\n const file = join(dir, 'graph.json')\n try {\n runNx(cwd, ['graph', '--file', file])\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as {\n graph?: { nodes?: Record<string, { data: { root: string } }> }\n }\n const nodes = parsed.graph?.nodes\n if (!nodes || typeof nodes !== 'object') {\n throw new Error(\n 'sentinel: unexpected nx graph output (no graph.nodes); the installed nx version may be incompatible.',\n )\n }\n return Object.entries(nodes).map(([name, node]) => ({\n name,\n root: join(cwd, node.data.root),\n }))\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nexport function discoverModules(cwd: string, options: { affected?: boolean } = {}): ModuleRef[] {\n const modules = readGraph(cwd)\n if (!options.affected) return modules\n const affected = new Set(\n JSON.parse(runNx(cwd, ['show', 'projects', '--affected', '--json'])) as string[],\n )\n return modules.filter((module) => affected.has(module.name))\n}\n","/**\n * Tunable settings: the knobs you would actually change. The fixed vocabulary and\n * its types live in `domain.ts`.\n *\n * Note: the flavour is DETECTED from a module's dependencies (see\n * `detect-framework.ts`), deterministically, `node` when no framework dep is present.\n * `--flavour` overrides the detection (recommended for `--update`, since hoisted deps\n * can make detection return `node`). The detection table lives in `detect-framework.ts`.\n */\n\n/** A file that marks a workspace root; sentinel refuses to operate there. */\nexport const WORKSPACE_ROOT_MARKER = 'nx.json'\n","/**\n * The domain vocabulary: the fixed sets of verbs, targets, and flavours, and the\n * types derived from them. This is the model, and the extension point: adding a\n * verb / target / flavour is a one-line edit to a list here, and because the types\n * are DERIVED (`(typeof LIST)[number]`), the compiler forces every switch/handler\n * to cover the new member.\n *\n * Tunable behaviour (defaults, detection signals, marker filenames) lives in\n * `settings.ts`, not here.\n */\n\n/** Verbs: what to do. Each maps to an adapter method in dispatch. */\nexport const VERBS = ['run', 'inspect', 'update', 'report', 'status'] as const\nexport type Verb = (typeof VERBS)[number]\n\n/** Targets: the kind of check. The CLI `--<target>` flags map 1:1 to these. */\nexport const TARGETS = [\n 'lint',\n 'format',\n 'typescript',\n 'build',\n 'test',\n 'static-analysis',\n 'runtime-analysis',\n 'arch',\n] as const\nexport type Target = (typeof TARGETS)[number]\n\n/** Flavours: the stack preset a project resolves to (strict by default). */\nexport const FLAVOURS = ['react', 'nest', 'svelte', 'node'] as const\nexport type Flavour = (typeof FLAVOURS)[number]\n\n/**\n * The outcome of one module × target check. `unsupported` is a distinct third state\n * (a requested target has no adapter yet): it must never read as a pass, and an\n * explicitly-requested unsupported target is a usage error (non-zero exit), while an\n * unsupported target swept in by `--all` is just skipped and shown.\n */\nexport type ResultStatus = 'ok' | 'failed' | 'unsupported'\n","/**\n * Orchestration for `--report`/`--inspect`: analyse a set of modules across a set\n * of targets, then shape the outcomes into one versioned, parseable summary.\n *\n * The engine resolves each module's flavour and adapter and calls the per-module\n * method; a target with no adapter yet is simply skipped. `generateSummaries`\n * builds the aggregate by reusing `generateSummary` for each result (one factory,\n * no duplicated shaping between the single- and multi-module paths).\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { ModuleRef } from './discover-modules.js'\nimport type { Flavour, ResultStatus, Target } from './domain.js'\nimport { availableTargets, resolve } from './registry.js'\nimport type { RunContext } from './types.js'\n\n/** One project × target outcome. */\nexport interface AnalyseResult {\n project: string\n target: Target\n flavour: Flavour\n ok: boolean\n /**\n * The three-state outcome. `ok` stays for back-compat (`status === 'ok'`), but\n * `status` is the source of truth: `unsupported` (no adapter yet) must never read\n * as a pass.\n */\n status: ResultStatus\n /** `--report`: metrics (errors, implicit-any); `--inspect`: the resolved config. */\n data: unknown\n}\n\nexport interface AnalyseParams {\n verb: 'run' | 'report' | 'inspect' | 'status'\n targets: readonly Target[]\n modules: readonly ModuleRef[]\n runner?: string\n /** Explicit `--flavour`, when declared; otherwise detected per module from deps. */\n flavour?: Flavour\n /**\n * Whether the targets were named by the user (vs swept in by `--all` / no target). When\n * true, a target with no adapter yields a visible per-module `unsupported` row; when\n * false, it is skipped silently per module (the envelope's `skipped` still lists it once,\n * so `--all` does not flood the output with N×M unsupported rows).\n */\n targetsExplicit: boolean\n ci: boolean\n fix: boolean\n /** Cap on structured diagnostics per module in `--report` (0 = no cap). */\n maxDiagnostics?: number\n /** Called after each module, for progress display. */\n onProgress?: (done: number, total: number, moduleName: string) => void\n}\n\n/** Run the analyse across modules × targets. Returns the results + worst exit code. */\nexport async function analyse(\n params: AnalyseParams,\n): Promise<{ results: AnalyseResult[]; worstCode: number }> {\n const results: AnalyseResult[] = []\n let worstCode = 0\n let done = 0\n\n for (const module of params.modules) {\n // Explicit --flavour wins; otherwise detect it from the module's deps and, when the\n // module looks mixed (e.g. a Sails backend that also pulls React), warn and point at\n // --flavour rather than choosing silently.\n let flavour: Flavour\n if (params.flavour) {\n flavour = params.flavour\n } else {\n const detection = describeFramework(readProjectPackageJson(module.root))\n flavour = detection.flavour\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: ${module.name}: flavour is ambiguous, detected \"${flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to be explicit.`,\n ) + '\\n',\n )\n }\n }\n const ctx: RunContext = {\n module: module.name,\n cwd: module.root,\n flavour,\n ci: params.ci,\n fix: params.fix,\n maxDiagnostics: params.maxDiagnostics,\n }\n for (const target of params.targets) {\n let adapter\n try {\n adapter = resolve(target, flavour, params.runner)\n } catch (error) {\n // No adapter for this target yet. When the user named it, emit a visible per-module\n // `unsupported` row so it never reads as a pass. When it was swept in by `--all`,\n // skip silently per module: the envelope's `skipped` still lists it once, so `--all`\n // does not flood the output with one unsupported row per module × unwired target.\n if (params.targetsExplicit) {\n const reason = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'unsupported',\n data: { reason },\n })\n }\n continue\n }\n // Isolate every check: one module/target that throws must not abort the whole\n // sweep. A failure becomes a `ok:false` row (with the error) so `--report --all`\n // still returns a complete picture across a large workspace.\n try {\n if (params.verb === 'run') {\n // Label the run so multi-module output (root/--all) is readable; the tool\n // streams its own output (stdio inherit) between headers.\n if (params.modules.length > 1) {\n const err = palette(process.stderr)\n process.stderr.write(\n `\\n ${err.accent('▶')} ${err.strong(module.name)} ${err.dim(`(${flavour})`)} ${target}\\n`,\n )\n }\n const result = await adapter.run(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'report') {\n const result = await adapter.report(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: result.metrics ?? {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'inspect') {\n const config = await adapter.inspect(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: true,\n status: 'ok',\n data: config,\n })\n } else {\n // status: adoption + conformity, read from the committed config (no tool run).\n // A drifted adopted module is a conformance failure; a non-adopted one is not.\n const status = await adapter.status(ctx)\n const ok = !status.adopted || status.conformant\n results.push({\n project: module.name,\n target,\n flavour,\n ok,\n status: ok ? 'ok' : 'failed',\n data: status,\n })\n if (!ok) worstCode = Math.max(worstCode, 1)\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'failed',\n data: { error: message },\n })\n worstCode = Math.max(worstCode, 1)\n }\n }\n params.onProgress?.((done += 1), params.modules.length, module.name)\n }\n\n return { results, worstCode }\n}\n\n/** The shaped summary for ONE result (the factory unit). */\nexport function generateSummary(result: AnalyseResult): Record<string, unknown> {\n const { project, target, flavour, ok, status, data } = result\n const details =\n data && typeof data === 'object' ? (data as Record<string, unknown>) : { value: data }\n return { project, target, flavour, ok, status, ...details }\n}\n\n/**\n * The aggregate, versioned envelope for MANY results, built from `generateSummary`.\n * `executed` / `skipped` split the REQUESTED targets by whether an adapter is wired, so an\n * agent sees what actually ran vs what was unsupported at the top level without scanning\n * every row (and independently of `--all` suppressing per-module unsupported rows).\n * schemaVersion is 2 since these fields are new to the shape.\n */\nexport function generateSummaries(\n results: readonly AnalyseResult[],\n requestedTargets: readonly Target[],\n): {\n schemaVersion: number\n executed: Target[]\n skipped: Target[]\n results: Record<string, unknown>[]\n} {\n const available = new Set(availableTargets())\n return {\n schemaVersion: 2,\n executed: requestedTargets.filter((t) => available.has(t)),\n skipped: requestedTargets.filter((t) => !available.has(t)),\n results: results.map(generateSummary),\n }\n}\n","/**\n * Human rendering of one summary row (`--run`/`--report`/`--inspect`), colored via a\n * `Palette`. Pure and stream-agnostic: it returns the text, the CLI writes it. `--json`\n * bypasses this entirely (machines get the raw envelope).\n *\n * The goal is legibility: the pass/fail mark and the module name read at a glance, the\n * error counts stand out when non-zero, and the phased `deferred` rules become their own\n * indented block instead of an inline JSON blob.\n */\nimport type { Palette } from '../shared/color.js'\n\n/** Keys carried by the row head (or redundant with it), never repeated in the details. */\nconst HEAD_KEYS = new Set([\n 'project',\n 'module',\n 'target',\n 'flavour',\n 'ok',\n 'status',\n 'reason',\n // The structured diagnostics are for `--json` consumers; the human row stays the concise\n // `errors=N` line rather than dumping every diagnostic inline.\n 'diagnostics',\n 'diagnosticsTruncated',\n])\n\n/** A deferred (phased) rule, as `--inspect` reports it. */\ninterface DeferredRule {\n rule: string\n phase: number\n reason: string\n}\n\nfunction isDeferredRules(value: unknown): value is DeferredRule[] {\n return (\n Array.isArray(value) &&\n value.every((entry) => typeof entry === 'object' && entry !== null && 'rule' in entry)\n )\n}\n\n/** Color a detail value: error counts are the signal, so red when >0, green at 0. */\nfunction renderValue(key: string, value: unknown, p: Palette): string {\n if ((key === 'errors' || key === 'implicitAny') && typeof value === 'number') {\n return value > 0 ? p.fail(String(value)) : p.ok(String(value))\n }\n // implicitAny is `deferred` when the rule is off (phase 1): honest, not a fake 0.\n if (key === 'implicitAny' && value === 'deferred') return p.warn('deferred')\n return typeof value === 'string' ? value : JSON.stringify(value)\n}\n\n/**\n * `unsupported` is a third state (a requested target with no adapter yet): a distinct dim\n * marker, never a ✓ (would read as a pass) nor a ✗ (would read as a failure of the code).\n * Shared by every row renderer (summary AND status) so the state can never render as a\n * legitimate outcome on one path and not the other.\n */\nfunction renderUnsupportedRow(item: Record<string, unknown>, p: Palette): string {\n const reason = typeof item.reason === 'string' ? ` ${p.dim(`(${item.reason})`)}` : ''\n const head = ` ${p.dim('·')} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n return `${head} ${p.dim('—')} ${p.warn('unsupported')}${reason}`\n}\n\n/**\n * One summary row → colored lines: a head line (`✓ name (flavour) target — key=value …`)\n * plus, when present, an indented `deferred` block listing the phased rules.\n */\nexport function renderSummary(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const ok = item.ok === true\n const mark = ok ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n // Split details: the phased `deferred` rules get their own block; everything else is\n // rendered inline as dim `key=value` pairs (the head keys are dropped as redundant).\n const entries = Object.entries(item).filter(([key]) => !HEAD_KEYS.has(key))\n const deferred = entries.find(([key]) => key === 'deferred')?.[1]\n const inline = entries\n .filter(([key]) => key !== 'deferred')\n .map(([key, value]) => `${p.dim(`${key}=`)}${renderValue(key, value, p)}`)\n .join(' ')\n\n const lines = [inline ? `${head} ${p.dim('—')} ${inline}` : head]\n\n if (isDeferredRules(deferred) && deferred.length > 0) {\n lines.push(` ${p.warn('deferred (phase 1, non-breaking):')}`)\n for (const { rule, phase, reason } of deferred) {\n lines.push(` ${p.warn('•')} ${p.strong(rule)} ${p.dim(`— phase ${phase}: ${reason}`)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Workspace adoption + conformity totals, for the `--status` footer / JSON. */\nexport interface Coverage {\n total: number\n adopted: number\n conformant: number\n drifted: number\n}\n\nexport function statusCoverage(items: readonly Record<string, unknown>[]): Coverage {\n // Only real status rows count toward coverage; an `unsupported` target (a requested\n // target with no adapter) must not inflate the denominator (`total`).\n const relevant = items.filter((i) => i.status !== 'unsupported')\n const adopted = relevant.filter((i) => i.adopted === true)\n const conformant = adopted.filter((i) => i.conformant === true)\n return {\n total: relevant.length,\n adopted: adopted.length,\n conformant: conformant.length,\n drifted: adopted.length - conformant.length,\n }\n}\n\n/** Short preset name for display (`@hublo/sentinel/tsconfig/nest` -> `nest`). */\nfunction presetShort(preset: unknown): string {\n return typeof preset === 'string' ? preset.replace('@hublo/sentinel/tsconfig/', '') : '?'\n}\n\n/**\n * One `--status` row, three states: not-adopted (dim `·`), adopted + conformant (green\n * `✓`), adopted + drifted (red `✗`, with the drifted keys named).\n */\nexport function renderStatusRow(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const adopted = item.adopted === true\n const conformant = item.conformant === true\n const mark = !adopted ? p.dim('·') : conformant ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n if (!adopted) return `${head} ${p.dim('— not adopted')}`\n if (conformant) {\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)}) conformant`)}`\n }\n const drift = Array.isArray(item.drift) ? item.drift.join(', ') : ''\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)})`)} ${p.fail(`drift: ${drift}`)}`\n}\n\n/** The `--status` footer: `coverage: X/N adopted · K/X conformant`. */\nexport function renderStatusSummary(items: readonly Record<string, unknown>[], p: Palette): string {\n const c = statusCoverage(items)\n const drift = c.drifted > 0 ? p.fail(` · ${c.drifted} drifted`) : ''\n return ` ${p.strong('coverage:')} ${c.adopted}/${c.total} adopted ${p.dim('·')} ${c.conformant}/${c.adopted} conformant${drift}`\n}\n","/**\n * Node version guard. sentinel colours output with `util.styleText`, added in Node 20.12, so\n * an older runtime (the tester ran serviceapp on Node 10) crashes cryptically. We check up\n * front and fail with a clear, actionable message instead. Kept as a pure function so it is\n * unit-testable without spawning Node. The `{ stream }` option we pass is newer (22.13) but\n * older 20.x ignore it gracefully, so 20.12 is the real floor (and matches CI on Node 20).\n */\n\n/** The minimum Node this CLI supports (the `util.styleText` floor). */\nexport const MIN_NODE = '20.12.0'\n\n/** Parse `24.15.0` (or `v24.15.0`) into `[major, minor, patch]`; missing parts are 0. */\nfunction parts(version: string): [number, number, number] {\n const [major = 0, minor = 0, patch = 0] = version\n .replace(/^v/, '')\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n return [major, minor, patch]\n}\n\n/**\n * Whether `current` (e.g. `process.versions.node`) meets `min`. Compares major, then minor,\n * then patch. Returns `{ ok }` and, when not, a clear message telling the user their version,\n * the required one, and how to switch.\n */\nexport function checkNodeVersion(\n current: string,\n min: string = MIN_NODE,\n): { ok: boolean; message?: string } {\n const [cMajor, cMinor, cPatch] = parts(current)\n const [mMajor, mMinor, mPatch] = parts(min)\n const ok =\n cMajor > mMajor ||\n (cMajor === mMajor && cMinor > mMinor) ||\n (cMajor === mMajor && cMinor === mMinor && cPatch >= mPatch)\n if (ok) return { ok: true }\n return {\n ok: false,\n message:\n `sentinel requires Node >= ${min}, but you are on ${current}. ` +\n `Switch with fnm/nvm (e.g. \\`fnm use ${mMajor}\\`) and re-run.`,\n }\n}\n"],"mappings":"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","names":["join","cwd","cwd","join","opts","flavour"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.11",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|