@fairfox/polly 0.82.0 → 0.83.0
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/cli/polly.js +22 -1
- package/dist/cli/polly.js.map +3 -3
- package/dist/tools/bdd/src/args.d.ts +21 -0
- package/dist/tools/bdd/src/bus-driver.d.ts +36 -0
- package/dist/tools/bdd/src/check-verify.d.ts +15 -0
- package/dist/tools/bdd/src/cli.d.ts +2 -0
- package/dist/tools/bdd/src/cli.js +701 -0
- package/dist/tools/bdd/src/cli.js.map +19 -0
- package/dist/tools/bdd/src/config.d.ts +9 -0
- package/dist/tools/bdd/src/extract.d.ts +2 -0
- package/dist/tools/bdd/src/index.d.ts +19 -0
- package/dist/tools/bdd/src/index.js +540 -0
- package/dist/tools/bdd/src/index.js.map +17 -0
- package/dist/tools/bdd/src/parse.d.ts +3 -0
- package/dist/tools/bdd/src/report.d.ts +6 -0
- package/dist/tools/bdd/src/run.d.ts +8 -0
- package/dist/tools/bdd/src/scaffold.d.ts +7 -0
- package/dist/tools/bdd/src/steps.d.ts +55 -0
- package/dist/tools/bdd/src/types.d.ts +145 -0
- package/dist/tools/bdd/src/witness.d.ts +23 -0
- package/dist/tools/gallery/src/cli.js +4 -3
- package/dist/tools/gallery/src/cli.js.map +3 -3
- package/dist/tools/mutate/src/cli.js +8 -1
- package/dist/tools/mutate/src/cli.js.map +3 -3
- package/dist/tools/quality/src/cli.js +304 -15
- package/dist/tools/quality/src/cli.js.map +6 -4
- package/dist/tools/quality/src/index.d.ts +2 -0
- package/dist/tools/quality/src/index.js +309 -15
- package/dist/tools/quality/src/index.js.map +6 -4
- package/dist/tools/quality/src/no-fixed-waits.d.ts +52 -0
- package/dist/tools/quality/src/no-tautology-ensures.d.ts +67 -0
- package/dist/tools/quality/src/plugins/core.d.ts +1 -1
- package/dist/tools/test/src/coverage-policy/cli.js +5 -1
- package/dist/tools/test/src/coverage-policy/cli.js.map +3 -3
- package/dist/tools/test/src/tiers/args.d.ts +5 -0
- package/dist/tools/test/src/tiers/cli.js +97 -23
- package/dist/tools/test/src/tiers/cli.js.map +9 -8
- package/dist/tools/test/src/tiers/index.d.ts +2 -1
- package/dist/tools/test/src/tiers/order.d.ts +25 -0
- package/dist/tools/test/src/tiers/types.d.ts +15 -0
- package/dist/tools/verify/src/cli.js +540 -15
- package/dist/tools/verify/src/cli.js.map +8 -4
- package/dist/tools/visualize/src/cli.js +17 -13
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +9 -16
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-fixed-waits conformance check.
|
|
3
|
+
*
|
|
4
|
+
* Bans fixed-duration sleeps: `page.waitForTimeout(n)`, `Bun.sleep(n)`, and a
|
|
5
|
+
* `setTimeout` wrapped in a promise purely to resolve it. A fixed sleep encodes
|
|
6
|
+
* an assumption about how fast the machine is — it passes on an idle machine
|
|
7
|
+
* and fails on a loaded one, which is the root cause of flaky tests. Wait on a
|
|
8
|
+
* real condition instead (a poll loop, a web-first assertion, a microtask
|
|
9
|
+
* flush), or use the project's one sanctioned delay primitive where the wait
|
|
10
|
+
* itself is the behaviour (rate-limit backoff, deliberate pacing).
|
|
11
|
+
*
|
|
12
|
+
* A plain `setTimeout(realCallback, n)` — a debounce, a throttle, a recursive
|
|
13
|
+
* poll step — is NOT flagged; only the promise-resolving shape is the
|
|
14
|
+
* anti-pattern. Point `excludeFiles` at the modules that define the blessed
|
|
15
|
+
* delay/poll primitives (they necessarily contain a `setTimeout`).
|
|
16
|
+
*
|
|
17
|
+
* Exported from `@fairfox/polly/quality` so consumers run it programmatically;
|
|
18
|
+
* polly registers it as the `polly:no-fixed-waits` plugin check. Ported from
|
|
19
|
+
* the rule lingua and fairfox each maintained independently.
|
|
20
|
+
*/
|
|
21
|
+
export interface FixedWaitViolation {
|
|
22
|
+
file: string;
|
|
23
|
+
line: number;
|
|
24
|
+
content: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
export interface NoFixedWaitsResult {
|
|
28
|
+
violations: FixedWaitViolation[];
|
|
29
|
+
print: () => void;
|
|
30
|
+
}
|
|
31
|
+
export interface NoFixedWaitsOptions {
|
|
32
|
+
rootDir: string;
|
|
33
|
+
/** Directory names skipped anywhere in the path. */
|
|
34
|
+
exclude?: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Path suffixes skipped entirely — point these at the blessed delay/poll
|
|
37
|
+
* primitives (e.g. `src/utils/poll.ts`) which legitimately call setTimeout.
|
|
38
|
+
*/
|
|
39
|
+
excludeFiles?: string[];
|
|
40
|
+
/** Glob of files to scan. Defaults to `**\/*.{ts,tsx}`. */
|
|
41
|
+
filePatterns?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Scan file text for fixed-wait violations. Exported so it can be unit-tested
|
|
45
|
+
* directly without touching the filesystem.
|
|
46
|
+
*/
|
|
47
|
+
export declare function scanText(content: string, filePath?: string): FixedWaitViolation[];
|
|
48
|
+
/**
|
|
49
|
+
* Run the no-fixed-waits check against a directory. Returns the violations
|
|
50
|
+
* plus a `print` for CLI output, mirroring `checkNoAsCasting`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkNoFixedWaits(options: NoFixedWaitsOptions): Promise<NoFixedWaitsResult>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-tautology-ensures conformance check.
|
|
3
|
+
*
|
|
4
|
+
* Refuses `ensures(...)` / `requires(...)` calls whose predicate (the
|
|
5
|
+
* first positional argument) is a tautology: a bare literal (`true`,
|
|
6
|
+
* `false`, `null`, `undefined`, a number, a string) or a comparison of
|
|
7
|
+
* two literals (`1 === 1`). These primitives are Polly's formal-verify
|
|
8
|
+
* contracts — `ensures`/`requires` (and the wider verify family the
|
|
9
|
+
* Stryker ignorer lists in `tools/verify/src/stryker`) compile away at
|
|
10
|
+
* runtime and translate to TLA+ assertions under verification. A
|
|
11
|
+
* tautological predicate therefore asserts nothing, yet reads in review
|
|
12
|
+
* like a genuine safety check. Swapping a real predicate for `true`
|
|
13
|
+
* looks like a refactor and silently disables the only guard on a
|
|
14
|
+
* one-step handler; this lint stops that rewrite from merging.
|
|
15
|
+
*
|
|
16
|
+
* This module exports the check logic as a library so consuming
|
|
17
|
+
* applications can import it from `@fairfox/polly/quality` and run it
|
|
18
|
+
* programmatically. It is registered as the `polly:no-tautology-ensures`
|
|
19
|
+
* plugin check, so it also runs via `polly quality run`.
|
|
20
|
+
*
|
|
21
|
+
* The scan is text-based, not AST-based: the only call sites are
|
|
22
|
+
* `ensures(<expr>, '<message>')` / `requires(<expr>, '<message>')` with
|
|
23
|
+
* the predicate as the first argument, so a balanced-paren walk that
|
|
24
|
+
* skips string and comment content is sufficient and keeps the false
|
|
25
|
+
* positive rate at zero.
|
|
26
|
+
*/
|
|
27
|
+
export interface TautologyViolation {
|
|
28
|
+
file: string;
|
|
29
|
+
line: number;
|
|
30
|
+
content: string;
|
|
31
|
+
reason: string;
|
|
32
|
+
}
|
|
33
|
+
export interface NoTautologyEnsuresResult {
|
|
34
|
+
violations: TautologyViolation[];
|
|
35
|
+
print: () => void;
|
|
36
|
+
}
|
|
37
|
+
export interface NoTautologyEnsuresOptions {
|
|
38
|
+
rootDir: string;
|
|
39
|
+
/** Directory names skipped anywhere in the path. */
|
|
40
|
+
exclude?: string[];
|
|
41
|
+
/** Basenames or relative paths skipped entirely. */
|
|
42
|
+
excludeFiles?: string[];
|
|
43
|
+
/** Glob of files to scan. Defaults to `**\/*.{ts,tsx}`. */
|
|
44
|
+
filePatterns?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Verify primitives whose first argument is a runtime-inert predicate.
|
|
47
|
+
* Defaults to the predicate-taking pair `ensures` / `requires`; pass a
|
|
48
|
+
* wider set (e.g. `invariant`, `stateConstraint`) to match a project's
|
|
49
|
+
* own usage.
|
|
50
|
+
*/
|
|
51
|
+
primitives?: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Classify a predicate string. Returns the tautology reason, or
|
|
55
|
+
* `undefined` when the predicate references state and is therefore a
|
|
56
|
+
* real assertion. Conservative by design: only the explicit literal and
|
|
57
|
+
* literal-vs-literal forms are flagged, so anything touching an
|
|
58
|
+
* identifier or property access passes.
|
|
59
|
+
*/
|
|
60
|
+
export declare function tautologyReason(predicate: string): string | undefined;
|
|
61
|
+
/** Split a call's argument list on top-level commas, skipping strings/nesting. */
|
|
62
|
+
export declare function splitTopLevelArgs(args: string): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Run the no-tautology-ensures check against a directory. Returns the
|
|
65
|
+
* violations plus a `print` for CLI output, mirroring `checkNoAsCasting`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function checkNoTautologyEnsures(options: NoTautologyEnsuresOptions): Promise<NoTautologyEnsuresResult>;
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
* contract polly-ui owns. Issue #98's scope is the four core checks.
|
|
15
15
|
*/
|
|
16
16
|
import type { QualityPlugin } from "../types";
|
|
17
|
-
export declare const POLLY_CORE_VERSION = "0.
|
|
17
|
+
export declare const POLLY_CORE_VERSION = "0.49.0";
|
|
18
18
|
export declare const pollyCorePlugin: QualityPlugin;
|
|
@@ -298,6 +298,10 @@ function reportMutate(report) {
|
|
|
298
298
|
function showHelp() {
|
|
299
299
|
process.stdout.write(`polly coverage — coverage policy, orphan detection, Stryker target validation
|
|
300
300
|
|
|
301
|
+
` + ` Coverage that a line ran isn't proof anything checks it. This enforces a
|
|
302
|
+
` + ` per-file floor with explicit, owned exemptions so test debt stays visible,
|
|
303
|
+
` + ` and flags orphans (source no test imports) and drifted Stryker targets.
|
|
304
|
+
|
|
301
305
|
` + ` --strict-orphans fail on source no unit test imports
|
|
302
306
|
` + ` --orphans list orphan files
|
|
303
307
|
` + ` --no-mutate skip the Stryker mutate/testFiles check
|
|
@@ -336,4 +340,4 @@ async function main() {
|
|
|
336
340
|
}
|
|
337
341
|
await main();
|
|
338
342
|
|
|
339
|
-
//# debugId=
|
|
343
|
+
//# debugId=7C14BCD836C3C95164756E2164756E21
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @fairfox/polly/test/coverage — the coverage-policy engine.\n *\n * Parses a `bun test --coverage` table and applies a {@link CoverageConfig}.\n * It fails on four conditions, not just low numbers: a non-exempt file below\n * the floor, an exempt key whose source no longer exists, an exemption whose\n * `claimedBy` test no longer exists, and an exempt file that has climbed back\n * over the floor (promote it). It also reports orphans — source files no unit\n * test imports, the blind spot a coverage table can't show.\n *\n * Everything here is parameterised by the project root, so the same engine\n * backs Polly's own gate and the consumer-facing `polly coverage` command.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { Glob } from \"bun\";\nimport type { CoverageConfig } from \"./types\";\n\nexport interface CoverageRow {\n /** Project-relative, e.g. `src/shared/lib/state.ts`. */\n file: string;\n funcs: number;\n lines: number;\n}\n\nexport interface Violation {\n file: string;\n metric: \"lines\" | \"funcs\";\n observed: number;\n required: number;\n}\n\nexport interface CoverageFindings {\n rowCount: number;\n violations: Violation[];\n staleExempts: string[];\n missingExemptFiles: string[];\n missingClaimedBy: Array<{ file: string; claimedBy: string }>;\n orphans: string[];\n /** True when a floor was configured; false means report-only. */\n enforced: boolean;\n}\n\nconst DEFAULT_SRC = \"src\";\n\n/** `../src/foo.ts` (run from a subdir) → `src/foo.ts` (project-relative). */\nfunction normalizePath(raw: string): string {\n return raw.replace(/^(?:\\.\\.\\/)+/, \"\");\n}\n\n/**\n * Parse the `bun test --coverage` table. Only rows under `srcDir` are\n * policy-bearing; the `All files` summary and test-infra rows are skipped.\n * Column order is `File | % Funcs | % Lines | Uncovered`.\n */\nexport function parseCoverageTable(text: string, srcDir: string): CoverageRow[] {\n const prefix = `${srcDir}/`;\n const rows: CoverageRow[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.includes(\"|\")) continue;\n if (line.includes(\"All files\") || line.includes(\"% Funcs\")) continue;\n if (line.trim().startsWith(\"---\")) continue;\n\n const cells = line.split(\"|\").map((c) => c.trim());\n if (cells.length < 3) continue;\n\n const file = normalizePath(cells[0] ?? \"\");\n const funcs = Number(cells[1]);\n const lines = Number(cells[2]);\n if (!file.startsWith(prefix) || Number.isNaN(funcs) || Number.isNaN(lines)) continue;\n\n rows.push({ file, funcs, lines });\n }\n return rows;\n}\n\n/** Run `bun test --coverage` in the configured cwd and return combined output. */\nexport async function runCoverage(root: string, testCwd: string): Promise<string> {\n const proc = Bun.spawn([\"bun\", \"test\", \"--coverage\"], {\n cwd: join(root, testCwd),\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n const [out, err] = await Promise.all([\n new Response(proc.stdout).text(),\n new Response(proc.stderr).text(),\n ]);\n await proc.exited;\n if (proc.exitCode !== 0) {\n throw new Error(`bun test --coverage exited ${proc.exitCode}\\n${err}`);\n }\n return `${out}\\n${err}`;\n}\n\nfunction evaluateRows(\n rows: CoverageRow[],\n config: CoverageConfig\n): { violations: Violation[]; staleExempts: string[] } {\n const t = config.defaultThreshold;\n const exempt = config.exempt ?? {};\n const violations: Violation[] = [];\n const staleExempts: string[] = [];\n if (!t) return { violations, staleExempts };\n\n for (const row of rows) {\n if (exempt[row.file]) {\n if (row.lines >= t.lines && row.funcs >= t.funcs) staleExempts.push(row.file);\n continue;\n }\n if (row.lines < t.lines) {\n violations.push({ file: row.file, metric: \"lines\", observed: row.lines, required: t.lines });\n }\n if (row.funcs < t.funcs) {\n violations.push({ file: row.file, metric: \"funcs\", observed: row.funcs, required: t.funcs });\n }\n }\n return { violations, staleExempts };\n}\n\nfunction validateExemptions(\n root: string,\n config: CoverageConfig\n): { missingExemptFiles: string[]; missingClaimedBy: Array<{ file: string; claimedBy: string }> } {\n const missingExemptFiles: string[] = [];\n const missingClaimedBy: Array<{ file: string; claimedBy: string }> = [];\n for (const [file, entry] of Object.entries(config.exempt ?? {})) {\n if (!existsSync(resolve(root, file))) missingExemptFiles.push(file);\n const claimedBy = entry.claimedBy.trim();\n if (!claimedBy.startsWith(\"n/a\") && !existsSync(resolve(root, claimedBy))) {\n missingClaimedBy.push({ file, claimedBy });\n }\n }\n return { missingExemptFiles, missingClaimedBy };\n}\n\n/** Source files no row covers and no exemption names — the coverage blind spot. */\nasync function findOrphans(\n root: string,\n srcDir: string,\n covered: Set<string>,\n config: CoverageConfig\n): Promise<string[]> {\n const exempt = config.exempt ?? {};\n const orphans: string[] = [];\n const glob = new Glob(`${srcDir}/**/*.{ts,tsx}`);\n for await (const file of glob.scan({ cwd: root, onlyFiles: true })) {\n if (file.endsWith(\".d.ts\") || /\\.test\\.tsx?$/.test(file) || file.includes(\"/__tests__/\")) {\n continue;\n }\n if (covered.has(file) || exempt[file]) continue;\n orphans.push(file);\n }\n return orphans.sort();\n}\n\n/** Apply the policy to a parsed table. Pure — no spawning. */\nexport async function evaluateCoverage(\n root: string,\n rows: CoverageRow[],\n config: CoverageConfig\n): Promise<CoverageFindings> {\n const srcDir = config.srcDir ?? DEFAULT_SRC;\n const { violations, staleExempts } = evaluateRows(rows, config);\n const { missingExemptFiles, missingClaimedBy } = validateExemptions(root, config);\n const orphans = await findOrphans(root, srcDir, new Set(rows.map((r) => r.file)), config);\n return {\n rowCount: rows.length,\n violations,\n staleExempts,\n missingExemptFiles,\n missingClaimedBy,\n orphans,\n enforced: config.defaultThreshold !== undefined,\n };\n}\n\n/** Run the suite under coverage and apply the policy. */\nexport async function enforceCoverage(\n root: string,\n config: CoverageConfig,\n coverageText?: string\n): Promise<CoverageFindings> {\n const srcDir = config.srcDir ?? DEFAULT_SRC;\n const text = coverageText ?? (await runCoverage(root, config.testCwd ?? \".\"));\n const rows = parseCoverageTable(text, srcDir);\n return evaluateCoverage(root, rows, config);\n}\n\n/** True when the findings represent a policy failure (orphans are advisory\n * unless `strictOrphans`). */\nexport function hasFailure(findings: CoverageFindings, strictOrphans: boolean): boolean {\n return (\n findings.violations.length > 0 ||\n findings.staleExempts.length > 0 ||\n findings.missingExemptFiles.length > 0 ||\n findings.missingClaimedBy.length > 0 ||\n (strictOrphans && findings.orphans.length > 0)\n );\n}\n",
|
|
6
6
|
"/**\n * @fairfox/polly/test/coverage — zero-config loading.\n *\n * `polly coverage` runs without any config: it looks for a `coverage.config.ts`\n * (or `.js`) at the project root, and if there is none it returns an empty\n * config, which the engine treats as report-only — it prints the numbers and\n * the orphan count but never fails the build. A consumer opts into enforcement\n * by adding a `defaultThreshold`, and into legible tiering by adding `exempt`\n * entries. This mirrors how `polly test --tier` discovers tiers by convention.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport type { CoverageConfig } from \"./types\";\n\nexport interface LoadedConfig {\n config: CoverageConfig;\n /** Absolute path the config was loaded from, or null for the zero-config default. */\n source: string | null;\n}\n\nfunction isCoverageConfig(value: unknown): value is CoverageConfig {\n return typeof value === \"object\" && value !== null;\n}\n\nasync function importConfig(path: string): Promise<CoverageConfig> {\n const mod: Record<string, unknown> = await import(path);\n const candidate = mod[\"config\"] ?? mod[\"default\"];\n if (!isCoverageConfig(candidate)) {\n throw new Error(`${path} must export \\`config\\` (a CoverageConfig object)`);\n }\n return candidate;\n}\n\n/**\n * Resolve the coverage config. With `explicitPath` (Polly's own front-end\n * passes scripts/coverage.config.ts) that file must exist. Otherwise look for\n * coverage.config.{ts,js} at the root; absent → the zero-config report-only\n * default.\n */\nexport async function loadCoverageConfig(\n root: string,\n explicitPath?: string\n): Promise<LoadedConfig> {\n if (explicitPath) {\n const abs = isAbsolute(explicitPath) ? explicitPath : resolve(root, explicitPath);\n if (!existsSync(abs)) throw new Error(`coverage config not found: ${abs}`);\n return { config: await importConfig(abs), source: abs };\n }\n\n for (const name of [\"coverage.config.ts\", \"coverage.config.js\"]) {\n const abs = join(root, name);\n if (existsSync(abs)) return { config: await importConfig(abs), source: abs };\n }\n return { config: {}, source: null };\n}\n",
|
|
7
|
-
"#!/usr/bin/env bun\n\n/**\n * @fairfox/polly/test/coverage — consumer-facing `polly coverage`.\n *\n * Zero-config: run it in any Polly project and it reports per-file coverage,\n * orphan source files, and (if a Stryker config is present) dead mutate/test\n * globs. Add a `coverage.config.ts` with a `defaultThreshold` to turn the\n * report into an enforced gate, and `exempt` entries to record which\n * higher-tier test covers a unit-thin file.\n *\n * Usage:\n * polly coverage # report (zero-config) or enforce (with config)\n * polly coverage --strict-orphans # fail on source no unit test imports\n * polly coverage --orphans # list the orphan files\n * polly coverage --no-mutate # skip the Stryker target check\n * polly coverage --config <path> # explicit coverage.config.ts\n * bun test --coverage | polly coverage --stdin\n */\n\nimport { loadCoverageConfig } from \"./discover\";\nimport { type CoverageFindings, enforceCoverage, hasFailure, parseCoverageTable } from \"./enforce\";\nimport { type MutateTargetReport, validateMutateTargets } from \"./mutate-targets\";\n\ninterface Args {\n root: string;\n configPath?: string;\n strictOrphans: boolean;\n listOrphans: boolean;\n stdin: boolean;\n mutate: boolean;\n help: boolean;\n}\n\nfunction parseArgs(argv: string[]): Args {\n const flag = (name: string) => argv.includes(name);\n const argValue = (name: string): string | undefined => {\n const i = argv.indexOf(name);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const strictOrphans = flag(\"--strict-orphans\");\n return {\n root: process.cwd(),\n configPath: argValue(\"--config\"),\n strictOrphans,\n listOrphans: strictOrphans || flag(\"--orphans\"),\n stdin: flag(\"--stdin\"),\n mutate: !flag(\"--no-mutate\"),\n help: flag(\"--help\") || flag(\"-h\"),\n };\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of Bun.stdin.stream()) chunks.push(new TextDecoder().decode(chunk));\n return chunks.join(\"\");\n}\n\nfunction reportPolicy(f: CoverageFindings): void {\n for (const m of f.missingExemptFiles) {\n process.stderr.write(`❌ exempt source missing: ${m}\\n`);\n }\n for (const { file, claimedBy } of f.missingClaimedBy) {\n process.stderr.write(`❌ ${file} → claimedBy missing: ${claimedBy}\\n`);\n }\n for (const s of f.staleExempts) {\n process.stderr.write(`❌ exempt file now meets the floor — promote it: ${s}\\n`);\n }\n for (const v of f.violations) {\n process.stderr.write(\n `❌ ${v.file} ${v.metric}=${v.observed.toFixed(2)}% (need ≥ ${v.required}%)\\n`\n );\n }\n}\n\nfunction reportOrphans(f: CoverageFindings, args: Args): void {\n if (f.orphans.length === 0) return;\n process.stderr.write(\n `${args.strictOrphans ? \"❌\" : \"⚠️ \"} ${f.orphans.length} src file(s) no unit test imports\\n`\n );\n if (args.listOrphans) for (const o of f.orphans) process.stderr.write(` ${o}\\n`);\n else process.stderr.write(\" --orphans to list, --strict-orphans to fail\\n\");\n}\n\nfunction reportMutate(report: MutateTargetReport): void {\n if (report.issues.length === 0) return;\n process.stderr.write(`❌ ${report.issues.length} Stryker target(s) resolve to no files:\\n`);\n for (const i of report.issues) {\n process.stderr.write(` ${i.config} [${i.field}] ${i.pattern}\\n`);\n }\n}\n\nfunction showHelp(): void {\n process.stdout.write(\n \"polly coverage — coverage policy, orphan detection, Stryker target validation\\n\\n\" +\n \" --strict-orphans fail on source no unit test imports\\n\" +\n \" --orphans list orphan files\\n\" +\n \" --no-mutate skip the Stryker mutate/testFiles check\\n\" +\n \" --config <path> explicit coverage.config.ts\\n\" +\n \" --stdin read a `bun test --coverage` table from stdin\\n\"\n );\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (args.help) {\n showHelp();\n return;\n }\n\n const { config, source } = await loadCoverageConfig(args.root, args.configPath);\n const srcDir = config.srcDir ?? \"src\";\n\n const findings = args.stdin\n ? await import(\"./enforce\").then(async (m) => {\n const rows = parseCoverageTable(await readStdin(), srcDir);\n return m.evaluateCoverage(args.root, rows, config);\n })\n : await enforceCoverage(args.root, config);\n\n reportPolicy(findings);\n reportOrphans(findings, args);\n\n let mutate: MutateTargetReport = { configs: [], issues: [] };\n if (args.mutate) {\n mutate = await validateMutateTargets(args.root);\n reportMutate(mutate);\n }\n\n const failed = hasFailure(findings, args.strictOrphans) || mutate.issues.length > 0;\n if (!failed) {\n const mode = findings.enforced ? `floor enforced` : \"report-only (no defaultThreshold)\";\n const where = source ? \"\" : \" — zero-config\";\n const orphanNote = findings.orphans.length ? `, ${findings.orphans.length} orphan` : \"\";\n const mutateNote = mutate.configs.length\n ? `, ${mutate.configs.length} stryker config(s) ok`\n : \"\";\n process.stdout.write(\n `✅ coverage ok — ${findings.rowCount} src files, ${mode}${where}${orphanNote}${mutateNote}\\n`\n );\n }\n process.exit(failed ? 1 : 0);\n}\n\nawait main();\n",
|
|
7
|
+
"#!/usr/bin/env bun\n\n/**\n * @fairfox/polly/test/coverage — consumer-facing `polly coverage`.\n *\n * Zero-config: run it in any Polly project and it reports per-file coverage,\n * orphan source files, and (if a Stryker config is present) dead mutate/test\n * globs. Add a `coverage.config.ts` with a `defaultThreshold` to turn the\n * report into an enforced gate, and `exempt` entries to record which\n * higher-tier test covers a unit-thin file.\n *\n * Usage:\n * polly coverage # report (zero-config) or enforce (with config)\n * polly coverage --strict-orphans # fail on source no unit test imports\n * polly coverage --orphans # list the orphan files\n * polly coverage --no-mutate # skip the Stryker target check\n * polly coverage --config <path> # explicit coverage.config.ts\n * bun test --coverage | polly coverage --stdin\n */\n\nimport { loadCoverageConfig } from \"./discover\";\nimport { type CoverageFindings, enforceCoverage, hasFailure, parseCoverageTable } from \"./enforce\";\nimport { type MutateTargetReport, validateMutateTargets } from \"./mutate-targets\";\n\ninterface Args {\n root: string;\n configPath?: string;\n strictOrphans: boolean;\n listOrphans: boolean;\n stdin: boolean;\n mutate: boolean;\n help: boolean;\n}\n\nfunction parseArgs(argv: string[]): Args {\n const flag = (name: string) => argv.includes(name);\n const argValue = (name: string): string | undefined => {\n const i = argv.indexOf(name);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const strictOrphans = flag(\"--strict-orphans\");\n return {\n root: process.cwd(),\n configPath: argValue(\"--config\"),\n strictOrphans,\n listOrphans: strictOrphans || flag(\"--orphans\"),\n stdin: flag(\"--stdin\"),\n mutate: !flag(\"--no-mutate\"),\n help: flag(\"--help\") || flag(\"-h\"),\n };\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of Bun.stdin.stream()) chunks.push(new TextDecoder().decode(chunk));\n return chunks.join(\"\");\n}\n\nfunction reportPolicy(f: CoverageFindings): void {\n for (const m of f.missingExemptFiles) {\n process.stderr.write(`❌ exempt source missing: ${m}\\n`);\n }\n for (const { file, claimedBy } of f.missingClaimedBy) {\n process.stderr.write(`❌ ${file} → claimedBy missing: ${claimedBy}\\n`);\n }\n for (const s of f.staleExempts) {\n process.stderr.write(`❌ exempt file now meets the floor — promote it: ${s}\\n`);\n }\n for (const v of f.violations) {\n process.stderr.write(\n `❌ ${v.file} ${v.metric}=${v.observed.toFixed(2)}% (need ≥ ${v.required}%)\\n`\n );\n }\n}\n\nfunction reportOrphans(f: CoverageFindings, args: Args): void {\n if (f.orphans.length === 0) return;\n process.stderr.write(\n `${args.strictOrphans ? \"❌\" : \"⚠️ \"} ${f.orphans.length} src file(s) no unit test imports\\n`\n );\n if (args.listOrphans) for (const o of f.orphans) process.stderr.write(` ${o}\\n`);\n else process.stderr.write(\" --orphans to list, --strict-orphans to fail\\n\");\n}\n\nfunction reportMutate(report: MutateTargetReport): void {\n if (report.issues.length === 0) return;\n process.stderr.write(`❌ ${report.issues.length} Stryker target(s) resolve to no files:\\n`);\n for (const i of report.issues) {\n process.stderr.write(` ${i.config} [${i.field}] ${i.pattern}\\n`);\n }\n}\n\nfunction showHelp(): void {\n process.stdout.write(\n \"polly coverage — coverage policy, orphan detection, Stryker target validation\\n\\n\" +\n \" Coverage that a line ran isn't proof anything checks it. This enforces a\\n\" +\n \" per-file floor with explicit, owned exemptions so test debt stays visible,\\n\" +\n \" and flags orphans (source no test imports) and drifted Stryker targets.\\n\\n\" +\n \" --strict-orphans fail on source no unit test imports\\n\" +\n \" --orphans list orphan files\\n\" +\n \" --no-mutate skip the Stryker mutate/testFiles check\\n\" +\n \" --config <path> explicit coverage.config.ts\\n\" +\n \" --stdin read a `bun test --coverage` table from stdin\\n\"\n );\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (args.help) {\n showHelp();\n return;\n }\n\n const { config, source } = await loadCoverageConfig(args.root, args.configPath);\n const srcDir = config.srcDir ?? \"src\";\n\n const findings = args.stdin\n ? await import(\"./enforce\").then(async (m) => {\n const rows = parseCoverageTable(await readStdin(), srcDir);\n return m.evaluateCoverage(args.root, rows, config);\n })\n : await enforceCoverage(args.root, config);\n\n reportPolicy(findings);\n reportOrphans(findings, args);\n\n let mutate: MutateTargetReport = { configs: [], issues: [] };\n if (args.mutate) {\n mutate = await validateMutateTargets(args.root);\n reportMutate(mutate);\n }\n\n const failed = hasFailure(findings, args.strictOrphans) || mutate.issues.length > 0;\n if (!failed) {\n const mode = findings.enforced ? `floor enforced` : \"report-only (no defaultThreshold)\";\n const where = source ? \"\" : \" — zero-config\";\n const orphanNote = findings.orphans.length ? `, ${findings.orphans.length} orphan` : \"\";\n const mutateNote = mutate.configs.length\n ? `, ${mutate.configs.length} stryker config(s) ok`\n : \"\";\n process.stdout.write(\n `✅ coverage ok — ${findings.rowCount} src files, ${mode}${where}${orphanNote}${mutateNote}\\n`\n );\n }\n process.exit(failed ? 1 : 0);\n}\n\nawait main();\n",
|
|
8
8
|
"/**\n * @fairfox/polly/test/coverage — Stryker mutate-/test-target validation.\n *\n * A Stryker config's `mutate` and `testFiles` lists are hand-curated. A path\n * that is renamed, or a glob whose directory moves, silently resolves to\n * nothing — Stryker mutates fewer files (or none) and the only signal is a\n * long mutation run whose score quietly drops. This asserts, in milliseconds,\n * that every entry still resolves to at least one file.\n *\n * Discovers configs two ways, covering both layouts in the wild: a single\n * `stryker.conf.json` at the root, and a `stryker/` directory of per-package\n * shards.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { Glob } from \"bun\";\n\ninterface StrykerConfig {\n mutate?: string[];\n testFiles?: string[];\n bun?: { testFiles?: string[] };\n}\n\nexport interface MutateTargetIssue {\n config: string;\n field: \"mutate\" | \"testFiles\";\n pattern: string;\n}\n\nexport interface MutateTargetReport {\n configs: string[];\n issues: MutateTargetIssue[];\n}\n\n/** Locate every Stryker config under the project root. */\nexport async function findStrykerConfigs(root: string): Promise<string[]> {\n const found: string[] = [];\n const single = join(root, \"stryker.conf.json\");\n if (existsSync(single)) found.push(single);\n\n const glob = new Glob(\"stryker/*.{json,conf.json}\");\n for await (const rel of glob.scan({ cwd: root, onlyFiles: true })) {\n found.push(join(root, rel));\n }\n return found.sort();\n}\n\nfunction isGlob(pattern: string): boolean {\n return pattern.includes(\"*\") || pattern.includes(\"?\") || pattern.includes(\"[\");\n}\n\nasync function resolvesToFile(pattern: string, cwd: string): Promise<boolean> {\n if (pattern.startsWith(\"!\")) return true; // negations are filters, not targets\n if (!isGlob(pattern)) return existsSync(resolve(cwd, pattern));\n const glob = new Glob(pattern);\n for await (const _ of glob.scan({ cwd, onlyFiles: true })) return true;\n return false;\n}\n\nasync function checkField(\n configPath: string,\n field: \"mutate\" | \"testFiles\",\n patterns: string[] | undefined,\n cwd: string\n): Promise<MutateTargetIssue[]> {\n const issues: MutateTargetIssue[] = [];\n for (const pattern of patterns ?? []) {\n if (!(await resolvesToFile(pattern, cwd))) {\n issues.push({ config: configPath, field, pattern });\n }\n }\n return issues;\n}\n\n/**\n * Validate every Stryker config under `root`. Paths in a Stryker config are\n * relative to that config's directory; for the monorepo-root configs that is\n * the root, so we resolve globs against `root`.\n */\nexport async function validateMutateTargets(root: string): Promise<MutateTargetReport> {\n const configs = await findStrykerConfigs(root);\n const issues: MutateTargetIssue[] = [];\n for (const configPath of configs) {\n const config = JSON.parse(readFileSync(configPath, \"utf8\")) as unknown as StrykerConfig;\n const testFiles = config.testFiles ?? config.bun?.testFiles;\n issues.push(...(await checkField(configPath, \"mutate\", config.mutate, root)));\n issues.push(...(await checkField(configPath, \"testFiles\", testFiles, root)));\n }\n return { configs, issues };\n}\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": "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",
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "7C14BCD836C3C95164756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* tools/test/src/tiers/cli.ts) accept the same flags; only their tier *sets*
|
|
6
6
|
* differ. This keeps the surface identical.
|
|
7
7
|
*/
|
|
8
|
+
import type { CaseOrder } from "./types";
|
|
8
9
|
export interface TierArgs {
|
|
9
10
|
/** Explicit tier names (positional or via --tier=a,b). */
|
|
10
11
|
tiers: string[];
|
|
@@ -14,6 +15,10 @@ export interface TierArgs {
|
|
|
14
15
|
full: boolean;
|
|
15
16
|
list: boolean;
|
|
16
17
|
bail: boolean;
|
|
18
|
+
/** --fail-fast: soft early-stop on the first failing case. */
|
|
19
|
+
failFast: boolean;
|
|
20
|
+
/** --order=fast|cost|default within-tier ordering, or null to leave unset. */
|
|
21
|
+
order: CaseOrder | null;
|
|
17
22
|
strictNeeds: boolean;
|
|
18
23
|
/** Path to write JSON results, or null. */
|
|
19
24
|
json: string | null;
|
|
@@ -28,11 +28,45 @@ var BOOL_FLAGS = {
|
|
|
28
28
|
"--full": "full",
|
|
29
29
|
"--list": "list",
|
|
30
30
|
"--bail": "bail",
|
|
31
|
+
"--fail-fast": "failFast",
|
|
31
32
|
"--strict-needs": "strictNeeds"
|
|
32
33
|
};
|
|
34
|
+
function parseOrder(value) {
|
|
35
|
+
if (value === "default" || value === "fast" || value === "cost")
|
|
36
|
+
return value;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
33
39
|
function listValue(arg, name) {
|
|
34
40
|
return arg.startsWith(name) ? arg.slice(name.length).split(",") : [];
|
|
35
41
|
}
|
|
42
|
+
function applyValueFlag(args, arg) {
|
|
43
|
+
if (arg === "--json") {
|
|
44
|
+
args.json = DEFAULT_JSON;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (arg.startsWith("--json=")) {
|
|
48
|
+
args.json = arg.slice("--json=".length);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (arg.startsWith("--tier=")) {
|
|
52
|
+
args.tiers.push(...listValue(arg, "--tier="));
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (arg.startsWith("--only=")) {
|
|
56
|
+
args.only.push(...listValue(arg, "--only="));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (arg.startsWith("--order=")) {
|
|
60
|
+
const order = parseOrder(arg.slice("--order=".length));
|
|
61
|
+
if (order === null) {
|
|
62
|
+
console.log("Unknown --order value (expected default, fast, or cost)");
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
args.order = order;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
36
70
|
function parseTierArgs(argv) {
|
|
37
71
|
const args = {
|
|
38
72
|
tiers: [],
|
|
@@ -41,6 +75,8 @@ function parseTierArgs(argv) {
|
|
|
41
75
|
full: false,
|
|
42
76
|
list: false,
|
|
43
77
|
bail: false,
|
|
78
|
+
failFast: false,
|
|
79
|
+
order: null,
|
|
44
80
|
strictNeeds: false,
|
|
45
81
|
json: null
|
|
46
82
|
};
|
|
@@ -50,20 +86,7 @@ function parseTierArgs(argv) {
|
|
|
50
86
|
args[boolKey] = true;
|
|
51
87
|
continue;
|
|
52
88
|
}
|
|
53
|
-
if (arg
|
|
54
|
-
args.json = DEFAULT_JSON;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (arg.startsWith("--json=")) {
|
|
58
|
-
args.json = arg.slice("--json=".length);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (arg.startsWith("--tier=")) {
|
|
62
|
-
args.tiers.push(...listValue(arg, "--tier="));
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (arg.startsWith("--only=")) {
|
|
66
|
-
args.only.push(...listValue(arg, "--only="));
|
|
89
|
+
if (applyValueFlag(args, arg)) {
|
|
67
90
|
continue;
|
|
68
91
|
}
|
|
69
92
|
if (arg.startsWith("-")) {
|
|
@@ -93,6 +116,11 @@ function browserRunner() {
|
|
|
93
116
|
const source = `${import.meta.dir}/../browser/run.ts`;
|
|
94
117
|
return existsSync(bundled) ? bundled : source;
|
|
95
118
|
}
|
|
119
|
+
function bddRunner() {
|
|
120
|
+
const bundled = `${import.meta.dir}/../../../bdd/src/cli.js`;
|
|
121
|
+
const source = `${import.meta.dir}/../../../bdd/src/cli.ts`;
|
|
122
|
+
return existsSync(bundled) ? bundled : source;
|
|
123
|
+
}
|
|
96
124
|
function browserDir(root, files) {
|
|
97
125
|
if (existsSync(join(root, "tests/browser")))
|
|
98
126
|
return "tests/browser";
|
|
@@ -136,6 +164,21 @@ async function discoverPlan(root) {
|
|
|
136
164
|
]
|
|
137
165
|
});
|
|
138
166
|
}
|
|
167
|
+
const featureFiles = await globFiles(root, "**/*.feature");
|
|
168
|
+
if (featureFiles.length > 0) {
|
|
169
|
+
tiers.push({
|
|
170
|
+
name: "bdd",
|
|
171
|
+
description: `executable Gherkin (${featureFiles.length} *.feature file(s))`,
|
|
172
|
+
timeoutMs: 120000,
|
|
173
|
+
cases: [
|
|
174
|
+
{
|
|
175
|
+
id: "bdd",
|
|
176
|
+
tags: ["bdd", "gherkin"],
|
|
177
|
+
exec: { kind: "command", argv: ["bun", bddRunner(), "run"], cwd: root }
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
139
182
|
const browserFiles = await globFiles(root, "**/*.browser.{ts,tsx}");
|
|
140
183
|
if (browserFiles.length > 0) {
|
|
141
184
|
tiers.push({
|
|
@@ -211,6 +254,22 @@ async function firstUnmetNeed(needs) {
|
|
|
211
254
|
return null;
|
|
212
255
|
}
|
|
213
256
|
|
|
257
|
+
// tools/test/src/tiers/order.ts
|
|
258
|
+
var COST_WEIGHT = {
|
|
259
|
+
light: 0,
|
|
260
|
+
medium: 1,
|
|
261
|
+
heavy: 2
|
|
262
|
+
};
|
|
263
|
+
function weightOf(spec) {
|
|
264
|
+
return COST_WEIGHT[spec.cost ?? "medium"];
|
|
265
|
+
}
|
|
266
|
+
function orderCases(cases, order) {
|
|
267
|
+
if (order === "default")
|
|
268
|
+
return cases;
|
|
269
|
+
const direction = order === "cost" ? -1 : 1;
|
|
270
|
+
return cases.map((spec, index) => ({ spec, index })).sort((a, b) => (weightOf(a.spec) - weightOf(b.spec)) * direction || a.index - b.index).map((entry) => entry.spec);
|
|
271
|
+
}
|
|
272
|
+
|
|
214
273
|
// tools/test/src/tiers/protocol.ts
|
|
215
274
|
var SENTINEL = "__TIER_RESULT__";
|
|
216
275
|
|
|
@@ -349,40 +408,52 @@ async function runOneCase(spec, tier, options, worker) {
|
|
|
349
408
|
}
|
|
350
409
|
async function runTier(tier, options, worker) {
|
|
351
410
|
const log = options.log ?? ((m) => console.log(m));
|
|
352
|
-
const
|
|
353
|
-
if (
|
|
411
|
+
const matched = tier.cases.filter((c) => caseMatches(c, options.only));
|
|
412
|
+
if (matched.length === 0)
|
|
354
413
|
return [];
|
|
414
|
+
const selected = orderCases(matched, options.order ?? "default");
|
|
355
415
|
log(`
|
|
356
416
|
▶ ${tier.name}${tier.description ? ` — ${tier.description}` : ""} (${selected.length} case${selected.length === 1 ? "" : "s"})`);
|
|
357
417
|
const concurrency = Math.max(1, tier.concurrency ?? 1);
|
|
418
|
+
const failFast = options.failFast ?? false;
|
|
358
419
|
const reports = new Array(selected.length);
|
|
359
420
|
let next = 0;
|
|
421
|
+
let aborted = false;
|
|
360
422
|
async function worker_loop() {
|
|
361
|
-
while (
|
|
423
|
+
while (!aborted) {
|
|
362
424
|
const i = next++;
|
|
363
425
|
const spec = selected[i];
|
|
364
426
|
if (!spec)
|
|
365
427
|
return;
|
|
366
|
-
|
|
428
|
+
const report = await runOneCase(spec, tier, options, worker);
|
|
429
|
+
reports[i] = report;
|
|
430
|
+
if (failFast && (report.outcome === "fail" || report.outcome === "timeout")) {
|
|
431
|
+
aborted = true;
|
|
432
|
+
}
|
|
367
433
|
}
|
|
368
434
|
}
|
|
369
435
|
await Promise.all(Array.from({ length: Math.min(concurrency, selected.length) }, worker_loop));
|
|
370
|
-
|
|
436
|
+
const done = reports.filter((r) => r !== undefined);
|
|
437
|
+
if (aborted && done.length < selected.length) {
|
|
438
|
+
log(` ⏹ fail-fast: stopped after a failure — ${selected.length - done.length} case(s) not run`);
|
|
439
|
+
}
|
|
440
|
+
return done;
|
|
371
441
|
}
|
|
372
442
|
async function runPlan(plan, options = {}) {
|
|
373
443
|
const log = options.log ?? ((m) => console.log(m));
|
|
374
444
|
const worker = await workerPath();
|
|
375
445
|
const wanted = options.tiers && options.tiers.length > 0 ? new Set(options.tiers) : null;
|
|
376
446
|
const started = performance.now();
|
|
447
|
+
const stopOnFail = Boolean(options.bail || options.failFast);
|
|
377
448
|
const all = [];
|
|
378
449
|
for (const tier of plan.tiers) {
|
|
379
450
|
if (wanted && !wanted.has(tier.name))
|
|
380
451
|
continue;
|
|
381
452
|
const reports = await runTier(tier, options, worker);
|
|
382
453
|
all.push(...reports);
|
|
383
|
-
if (
|
|
454
|
+
if (stopOnFail && reports.some((r) => r.outcome === "fail" || r.outcome === "timeout")) {
|
|
384
455
|
log(`
|
|
385
|
-
⏹
|
|
456
|
+
⏹ ${options.failFast ? "fail-fast" : "bail"}: stopping after failing tier "${tier.name}"`);
|
|
386
457
|
break;
|
|
387
458
|
}
|
|
388
459
|
}
|
|
@@ -427,7 +498,8 @@ function formatPlan(plan) {
|
|
|
427
498
|
lines.push(`${tier.name}${conc}${tier.description ? ` — ${tier.description}` : ""}`);
|
|
428
499
|
for (const c of tier.cases) {
|
|
429
500
|
const needs = c.needs && c.needs.length > 0 ? ` (needs ${c.needs.join(", ")})` : "";
|
|
430
|
-
|
|
501
|
+
const cost = c.cost ? ` [${c.cost}]` : "";
|
|
502
|
+
lines.push(` ${c.id}${cost}${needs}`);
|
|
431
503
|
}
|
|
432
504
|
lines.push("");
|
|
433
505
|
}
|
|
@@ -471,7 +543,9 @@ async function main() {
|
|
|
471
543
|
const report = await runPlan(plan, {
|
|
472
544
|
tiers,
|
|
473
545
|
only: args.only,
|
|
546
|
+
order: args.order ?? (args.failFast ? "fast" : "default"),
|
|
474
547
|
bail: args.bail,
|
|
548
|
+
failFast: args.failFast,
|
|
475
549
|
strictNeeds: args.strictNeeds,
|
|
476
550
|
cwd: root,
|
|
477
551
|
log: (m) => console.log(m)
|
|
@@ -487,4 +561,4 @@ wrote ${args.json}`);
|
|
|
487
561
|
}
|
|
488
562
|
await main();
|
|
489
563
|
|
|
490
|
-
//# debugId=
|
|
564
|
+
//# debugId=12E113E54144234E64756E2164756E21
|