@fairfox/polly 0.79.0 → 0.80.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 +31 -1
- package/dist/cli/polly.js.map +3 -3
- package/dist/tools/test/src/coverage-policy/cli.d.ts +19 -0
- package/dist/tools/test/src/coverage-policy/cli.js +339 -0
- package/dist/tools/test/src/coverage-policy/cli.js.map +13 -0
- package/dist/tools/test/src/coverage-policy/discover.d.ts +23 -0
- package/dist/tools/test/src/coverage-policy/enforce.d.ts +54 -0
- package/dist/tools/test/src/coverage-policy/index.d.ts +10 -0
- package/dist/tools/test/src/coverage-policy/index.js +242 -0
- package/dist/tools/test/src/coverage-policy/index.js.map +13 -0
- package/dist/tools/test/src/coverage-policy/mutate-targets.d.ts +30 -0
- package/dist/tools/test/src/coverage-policy/types.d.ts +35 -0
- package/dist/tools/test/src/e2e-mesh/index.js +18 -2
- package/dist/tools/test/src/e2e-mesh/index.js.map +5 -4
- package/dist/tools/test/src/e2e-mesh/wait-for-convergence.d.ts +8 -0
- package/dist/tools/test/src/e2e-relay/index.d.ts +1 -1
- package/dist/tools/test/src/e2e-relay/index.js +1421 -0
- package/dist/tools/test/src/e2e-relay/index.js.map +30 -0
- package/dist/tools/test/src/e2e-relay/wait-for-relay-convergence.d.ts +8 -0
- package/dist/tools/test/src/e2e-relay/with-repo-server.d.ts +9 -0
- package/dist/tools/test/src/e2e-shared/index.d.ts +1 -0
- package/dist/tools/test/src/e2e-shared/timeout-context.d.ts +17 -0
- package/dist/tools/test/src/index.d.ts +1 -0
- package/dist/tools/test/src/index.js +16 -1
- package/dist/tools/test/src/index.js.map +5 -4
- package/package.json +11 -1
package/dist/cli/polly.js
CHANGED
|
@@ -176,6 +176,31 @@ async function testTiers() {
|
|
|
176
176
|
throw new Error(`Tiered tests failed with exit code ${exitCode}`);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
async function coverage() {
|
|
180
|
+
const monorepoMarker = `${__dirname2}/../scripts/coverage.config.ts`;
|
|
181
|
+
const sourceCli = `${__dirname2}/../tools/test/src/coverage-policy/cli.ts`;
|
|
182
|
+
const bundledCli = `${__dirname2}/../tools/test/src/coverage-policy/cli.js`;
|
|
183
|
+
const isMonorepo = await Bun.file(monorepoMarker).exists();
|
|
184
|
+
let cliPath = sourceCli;
|
|
185
|
+
if (!isMonorepo && await Bun.file(bundledCli).exists()) {
|
|
186
|
+
cliPath = bundledCli;
|
|
187
|
+
}
|
|
188
|
+
const args2 = [...commandArgs];
|
|
189
|
+
if (isMonorepo) {
|
|
190
|
+
args2.unshift("--config", "scripts/coverage.config.ts", "--no-mutate");
|
|
191
|
+
}
|
|
192
|
+
const proc = Bun.spawn(["bun", cliPath, ...args2], {
|
|
193
|
+
cwd,
|
|
194
|
+
stdout: "inherit",
|
|
195
|
+
stderr: "inherit",
|
|
196
|
+
stdin: "inherit",
|
|
197
|
+
env: process.env
|
|
198
|
+
});
|
|
199
|
+
const exitCode = await proc.exited;
|
|
200
|
+
if (exitCode !== 0) {
|
|
201
|
+
throw new Error(`Coverage check failed with exit code ${exitCode}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
179
204
|
async function test() {
|
|
180
205
|
const bundledCli = `${__dirname2}/../tools/test/src/cli.js`;
|
|
181
206
|
const monorepoCli = `${__dirname2}/../tools/test/src/cli.ts`;
|
|
@@ -261,6 +286,8 @@ Usage:
|
|
|
261
286
|
Run the multi-tier suite (unit \u2192 integration
|
|
262
287
|
\u2192 browser \u2192 e2e \u2192 verify) with timed reports
|
|
263
288
|
polly test:browser [dir] Run *.browser.{ts,tsx} in Puppeteer
|
|
289
|
+
polly coverage [flags] Per-file coverage policy, orphan detection,
|
|
290
|
+
and Stryker target validation (zero-config)
|
|
264
291
|
polly verify [args] Run formal verification
|
|
265
292
|
polly visualize [args] Generate architecture diagrams
|
|
266
293
|
polly quality [args] Run quality conformance checks
|
|
@@ -306,6 +333,9 @@ async function main() {
|
|
|
306
333
|
case "test:browser":
|
|
307
334
|
await testBrowser();
|
|
308
335
|
break;
|
|
336
|
+
case "coverage":
|
|
337
|
+
await coverage();
|
|
338
|
+
break;
|
|
309
339
|
case "verify":
|
|
310
340
|
await verify();
|
|
311
341
|
break;
|
|
@@ -335,4 +365,4 @@ async function main() {
|
|
|
335
365
|
}
|
|
336
366
|
main();
|
|
337
367
|
|
|
338
|
-
//# debugId=
|
|
368
|
+
//# debugId=9670061BCD90AC2664756E2164756E21
|
package/dist/cli/polly.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../cli/polly.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"#!/usr/bin/env bun\n/**\n * Polly CLI\n *\n * Command-line tool for building multi-execution-context applications\n * with reactive state and cross-context messaging.\n *\n * Supports: Chrome extensions, PWAs, Node/Bun/Deno apps with workers\n *\n * Usage:\n * polly init [name] [--type=TYPE] Create a new project\n * polly check Run all checks (typecheck, lint, test, build)\n * polly build [options] Build the project\n * polly dev Build with watch mode\n * polly typecheck Type check your code\n * polly lint [--fix] Lint your code\n * polly format Format your code\n * polly test [args] Run tests (requires bun test)\n * polly test:browser [dir] Run *.browser.{ts,tsx} in Puppeteer\n * polly verify [args] Run formal verification\n * polly visualize [args] Generate architecture diagrams\n * polly quality [args] Run quality conformance checks\n * polly help Show help\n *\n * Project Types (init --type):\n * extension Chrome/Firefox extension (default)\n * pwa Progressive Web App with workers\n * websocket WebSocket server application\n * generic Generic TypeScript project\n *\n * Options:\n * --prod Build for production (minified)\n * --config <path> Path to config file (default: polly.config.ts)\n * --fix Auto-fix lint/format issues\n * --type=TYPE Project type for init command\n */\n\n// Use Bun built-ins instead of Node.js APIs\nconst __dirname = import.meta.dir;\n\nconst command = process.argv[2];\nconst commandArgs = process.argv.slice(3);\nconst cwd = process.cwd();\n\n// Parse arguments\nconst args = {\n prod: process.argv.includes(\"--prod\"),\n config: process.argv.includes(\"--config\")\n ? process.argv[process.argv.indexOf(\"--config\") + 1]\n : undefined,\n};\n\n/**\n * Load user's configuration\n */\nasync function loadConfig() {\n const configPaths = [\n args.config,\n `${cwd}/polly.config.ts`,\n `${cwd}/polly.config.js`,\n `${cwd}/polly.config.mjs`,\n ].filter(Boolean) as unknown as string[];\n\n for (const configPath of configPaths) {\n // Use Bun.file().exists() instead of existsSync\n if (await Bun.file(configPath).exists()) {\n try {\n const config = await import(configPath);\n return config.default || config;\n } catch (error) {\n console.log(`❌ Failed to load config: ${configPath}`);\n throw error;\n }\n }\n }\n return {\n srcDir: \"src\",\n distDir: \"dist\",\n manifest: \"manifest.json\",\n };\n}\n\n/**\n * Build command - build the extension\n */\nasync function build() {\n const config = await loadConfig();\n\n // Check if bundled (published) or in monorepo\n const bundledScript = `${__dirname}/../scripts/build-extension.js`;\n const monorepoScript = `${__dirname}/../scripts/build-extension.ts`;\n const buildScriptPath = (await Bun.file(bundledScript).exists()) ? bundledScript : monorepoScript;\n\n // Pass config via environment\n process.env[\"WEB_EXT_SRC\"] = `${cwd}/${config.srcDir || \"src\"}`;\n process.env[\"WEB_EXT_DIST\"] = `${cwd}/${config.distDir || \"dist\"}`;\n process.env[\"WEB_EXT_MANIFEST\"] = `${cwd}/${config.manifest || \"manifest.json\"}`;\n process.env[\"WEB_EXT_CWD\"] = cwd;\n process.env[\"WEB_EXT_PROD\"] = args.prod ? \"true\" : \"false\";\n\n // Run build\n const proc = Bun.spawn([\"bun\", buildScriptPath], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n process.exit(exitCode);\n }\n}\n\n/**\n * Dev command - build with watch mode\n */\nasync function dev() {\n await build();\n}\n\n/**\n * Verify command - delegate to @fairfox/web-ext-verify\n */\nasync function verify() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/verify/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/verify/src/cli.ts`;\n const verifyCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", verifyCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Verification failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Visualize command - delegate to @fairfox/polly-visualize\n */\nasync function visualize() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/visualize/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/visualize/src/cli.ts`;\n const visualizeCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", visualizeCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Visualization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Quality command - delegate to @fairfox/polly-quality\n */\nasync function quality() {\n const bundledCli = `${__dirname}/../tools/quality/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/quality/src/cli.ts`;\n const qualityCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", qualityCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Quality checks failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Typecheck command - run TypeScript type checking\n */\nasync function typecheck() {\n const proc = Bun.spawn([\"bunx\", \"tsc\", \"--noEmit\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Type checking failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Lint command - run Biome linter\n */\nasync function lint() {\n const fix = commandArgs.includes(\"--fix\");\n const lintArgs = fix ? [\"check\", \"--write\", \".\"] : [\"check\", \".\"];\n\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", ...lintArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Linting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Format command - run Biome formatter\n */\nasync function format() {\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", \"format\", \"--write\", \".\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Formatting failed with exit code ${exitCode}`);\n }\n}\n\n/** Flags that switch `polly test` into the tiered runner. */\nconst TIER_MODE_FLAGS = [\n \"--tier\",\n \"--all\",\n \"--full\",\n \"--list\",\n \"--only\",\n \"--bail\",\n \"--strict-needs\",\n];\n\n/** True when `polly test` should run the multi-tier orchestrator. */\nfunction isTierMode(rawArgs: string[]): boolean {\n return rawArgs.some(\n (a) => TIER_MODE_FLAGS.includes(a) || a.startsWith(\"--tier\") || a.startsWith(\"--only\")\n );\n}\n\n/**\n * Tiered test command — `polly test --tier=… / --all / --full / --list`.\n * Drives the multi-tier engine with structured, timed reporting.\n *\n * Dispatch is by binary location, not cwd: inside the Polly repo (source\n * binary) it runs Polly's OWN suite via scripts/test/cli.ts; for an installed\n * package (scripts/ isn't shipped) it runs consumer auto-discovery, which\n * builds a plan from the consumer's own tests (tools/test/src/tiers/cli).\n */\nasync function testTiers() {\n const monorepoCli = `${__dirname}/../scripts/test/cli.ts`;\n const bundledConsumer = `${__dirname}/../tools/test/src/tiers/cli.js`;\n const sourceConsumer = `${__dirname}/../tools/test/src/tiers/cli.ts`;\n\n let runnerCli: string;\n if (await Bun.file(monorepoCli).exists()) {\n runnerCli = monorepoCli; // in the Polly repo: Polly's own tiered suite\n } else if (await Bun.file(bundledConsumer).exists()) {\n runnerCli = bundledConsumer; // installed package: discover the consumer's tiers\n } else {\n runnerCli = sourceConsumer;\n }\n\n const proc = Bun.spawn([\"bun\", runnerCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tiered tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Test command - delegate to @fairfox/polly-test\n */\nasync function test() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/test/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/cli.ts`;\n const testCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", testCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Browser test command — bundles *.browser.{ts,tsx} files, serves them,\n * opens a Puppeteer page, and collects results. Consumers use this to\n * run tests written with @fairfox/polly/test/browser in a real browser.\n */\nasync function testBrowser() {\n const bundledCli = `${__dirname}/../tools/test/src/browser/run.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/browser/run.ts`;\n const browserCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", browserCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Browser tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Check command - run all quality checks in sequence\n */\nasync function check() {\n const checks = [\n { name: \"Type checking\", fn: typecheck },\n { name: \"Linting\", fn: lint },\n { name: \"Testing\", fn: test },\n { name: \"Building\", fn: build },\n { name: \"Verification\", fn: verify, optional: true },\n { name: \"Visualization\", fn: visualize, optional: true },\n ];\n\n for (const { name, fn, optional } of checks) {\n try {\n await fn();\n } catch (_error) {\n if (optional) {\n continue;\n }\n console.log(`\\n\\x1b[31m✗ ${name} failed\\x1b[0m\\n`);\n process.exit(1);\n }\n }\n}\n\n/**\n * Init command - delegate to @fairfox/polly-init\n */\nasync function init() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/init/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/init/src/cli.ts`;\n const initCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", initCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Initialization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Help command — prints usage from the docstring at the top of this file\n */\nfunction help() {\n console.log(`Polly CLI — multi-execution-context framework\n\nUsage:\n polly init [name] [--type=TYPE] Create a new project\n polly check Run all checks (typecheck, lint, test, build)\n polly build [options] Build the project\n polly dev Build with watch mode\n polly typecheck Type check your code\n polly lint [--fix] Lint your code\n polly format Format your code\n polly test [args] Run tests (bun test)\n polly test --tier=T | --all | --full | --list\n Run the multi-tier suite (unit → integration\n → browser → e2e → verify) with timed reports\n polly test:browser [dir] Run *.browser.{ts,tsx} in Puppeteer\n polly verify [args] Run formal verification\n polly visualize [args] Generate architecture diagrams\n polly quality [args] Run quality conformance checks\n polly help Show this help\n\nOptions:\n --prod Build for production (minified)\n --config <path> Path to config file (default: polly.config.ts)\n --fix Auto-fix lint/format issues\n --type=TYPE Project type for init command (pwa, extension, websocket, generic)`);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n try {\n switch (command) {\n case \"init\":\n await init();\n break;\n case \"check\":\n await check();\n break;\n case \"build\":\n await build();\n break;\n case \"dev\":\n await dev();\n break;\n case \"typecheck\":\n await typecheck();\n break;\n case \"lint\":\n await lint();\n break;\n case \"format\":\n await format();\n break;\n case \"test\":\n if (isTierMode(commandArgs)) {\n await testTiers();\n } else {\n await test();\n }\n break;\n case \"test:browser\":\n await testBrowser();\n break;\n case \"verify\":\n await verify();\n break;\n case \"visualize\":\n await visualize();\n break;\n case \"quality\":\n await quality();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n help();\n break;\n default:\n console.log(`❌ Unknown command: ${command}\\n`);\n help();\n process.exit(1);\n }\n } catch (error) {\n console.log(\"\\n❌ Command failed:\", error);\n process.exit(1);\n }\n}\n\nmain();\n"
|
|
5
|
+
"#!/usr/bin/env bun\n/**\n * Polly CLI\n *\n * Command-line tool for building multi-execution-context applications\n * with reactive state and cross-context messaging.\n *\n * Supports: Chrome extensions, PWAs, Node/Bun/Deno apps with workers\n *\n * Usage:\n * polly init [name] [--type=TYPE] Create a new project\n * polly check Run all checks (typecheck, lint, test, build)\n * polly build [options] Build the project\n * polly dev Build with watch mode\n * polly typecheck Type check your code\n * polly lint [--fix] Lint your code\n * polly format Format your code\n * polly test [args] Run tests (requires bun test)\n * polly test:browser [dir] Run *.browser.{ts,tsx} in Puppeteer\n * polly coverage [flags] Coverage policy + orphan + Stryker checks\n * polly verify [args] Run formal verification\n * polly visualize [args] Generate architecture diagrams\n * polly quality [args] Run quality conformance checks\n * polly help Show help\n *\n * Project Types (init --type):\n * extension Chrome/Firefox extension (default)\n * pwa Progressive Web App with workers\n * websocket WebSocket server application\n * generic Generic TypeScript project\n *\n * Options:\n * --prod Build for production (minified)\n * --config <path> Path to config file (default: polly.config.ts)\n * --fix Auto-fix lint/format issues\n * --type=TYPE Project type for init command\n */\n\n// Use Bun built-ins instead of Node.js APIs\nconst __dirname = import.meta.dir;\n\nconst command = process.argv[2];\nconst commandArgs = process.argv.slice(3);\nconst cwd = process.cwd();\n\n// Parse arguments\nconst args = {\n prod: process.argv.includes(\"--prod\"),\n config: process.argv.includes(\"--config\")\n ? process.argv[process.argv.indexOf(\"--config\") + 1]\n : undefined,\n};\n\n/**\n * Load user's configuration\n */\nasync function loadConfig() {\n const configPaths = [\n args.config,\n `${cwd}/polly.config.ts`,\n `${cwd}/polly.config.js`,\n `${cwd}/polly.config.mjs`,\n ].filter(Boolean) as unknown as string[];\n\n for (const configPath of configPaths) {\n // Use Bun.file().exists() instead of existsSync\n if (await Bun.file(configPath).exists()) {\n try {\n const config = await import(configPath);\n return config.default || config;\n } catch (error) {\n console.log(`❌ Failed to load config: ${configPath}`);\n throw error;\n }\n }\n }\n return {\n srcDir: \"src\",\n distDir: \"dist\",\n manifest: \"manifest.json\",\n };\n}\n\n/**\n * Build command - build the extension\n */\nasync function build() {\n const config = await loadConfig();\n\n // Check if bundled (published) or in monorepo\n const bundledScript = `${__dirname}/../scripts/build-extension.js`;\n const monorepoScript = `${__dirname}/../scripts/build-extension.ts`;\n const buildScriptPath = (await Bun.file(bundledScript).exists()) ? bundledScript : monorepoScript;\n\n // Pass config via environment\n process.env[\"WEB_EXT_SRC\"] = `${cwd}/${config.srcDir || \"src\"}`;\n process.env[\"WEB_EXT_DIST\"] = `${cwd}/${config.distDir || \"dist\"}`;\n process.env[\"WEB_EXT_MANIFEST\"] = `${cwd}/${config.manifest || \"manifest.json\"}`;\n process.env[\"WEB_EXT_CWD\"] = cwd;\n process.env[\"WEB_EXT_PROD\"] = args.prod ? \"true\" : \"false\";\n\n // Run build\n const proc = Bun.spawn([\"bun\", buildScriptPath], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n process.exit(exitCode);\n }\n}\n\n/**\n * Dev command - build with watch mode\n */\nasync function dev() {\n await build();\n}\n\n/**\n * Verify command - delegate to @fairfox/web-ext-verify\n */\nasync function verify() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/verify/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/verify/src/cli.ts`;\n const verifyCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", verifyCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Verification failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Visualize command - delegate to @fairfox/polly-visualize\n */\nasync function visualize() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/visualize/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/visualize/src/cli.ts`;\n const visualizeCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", visualizeCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Visualization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Quality command - delegate to @fairfox/polly-quality\n */\nasync function quality() {\n const bundledCli = `${__dirname}/../tools/quality/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/quality/src/cli.ts`;\n const qualityCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", qualityCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Quality checks failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Typecheck command - run TypeScript type checking\n */\nasync function typecheck() {\n const proc = Bun.spawn([\"bunx\", \"tsc\", \"--noEmit\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Type checking failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Lint command - run Biome linter\n */\nasync function lint() {\n const fix = commandArgs.includes(\"--fix\");\n const lintArgs = fix ? [\"check\", \"--write\", \".\"] : [\"check\", \".\"];\n\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", ...lintArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Linting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Format command - run Biome formatter\n */\nasync function format() {\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", \"format\", \"--write\", \".\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Formatting failed with exit code ${exitCode}`);\n }\n}\n\n/** Flags that switch `polly test` into the tiered runner. */\nconst TIER_MODE_FLAGS = [\n \"--tier\",\n \"--all\",\n \"--full\",\n \"--list\",\n \"--only\",\n \"--bail\",\n \"--strict-needs\",\n];\n\n/** True when `polly test` should run the multi-tier orchestrator. */\nfunction isTierMode(rawArgs: string[]): boolean {\n return rawArgs.some(\n (a) => TIER_MODE_FLAGS.includes(a) || a.startsWith(\"--tier\") || a.startsWith(\"--only\")\n );\n}\n\n/**\n * Tiered test command — `polly test --tier=… / --all / --full / --list`.\n * Drives the multi-tier engine with structured, timed reporting.\n *\n * Dispatch is by binary location, not cwd: inside the Polly repo (source\n * binary) it runs Polly's OWN suite via scripts/test/cli.ts; for an installed\n * package (scripts/ isn't shipped) it runs consumer auto-discovery, which\n * builds a plan from the consumer's own tests (tools/test/src/tiers/cli).\n */\nasync function testTiers() {\n const monorepoCli = `${__dirname}/../scripts/test/cli.ts`;\n const bundledConsumer = `${__dirname}/../tools/test/src/tiers/cli.js`;\n const sourceConsumer = `${__dirname}/../tools/test/src/tiers/cli.ts`;\n\n let runnerCli: string;\n if (await Bun.file(monorepoCli).exists()) {\n runnerCli = monorepoCli; // in the Polly repo: Polly's own tiered suite\n } else if (await Bun.file(bundledConsumer).exists()) {\n runnerCli = bundledConsumer; // installed package: discover the consumer's tiers\n } else {\n runnerCli = sourceConsumer;\n }\n\n const proc = Bun.spawn([\"bun\", runnerCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tiered tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Coverage command — per-file coverage policy, orphan detection, and Stryker\n * mutate-target validation. Zero-config in a consumer project; inside the\n * Polly repo it runs against Polly's own coverage.config.ts.\n */\nasync function coverage() {\n const monorepoMarker = `${__dirname}/../scripts/coverage.config.ts`;\n const sourceCli = `${__dirname}/../tools/test/src/coverage-policy/cli.ts`;\n const bundledCli = `${__dirname}/../tools/test/src/coverage-policy/cli.js`;\n\n const isMonorepo = await Bun.file(monorepoMarker).exists();\n let cliPath = sourceCli;\n if (!isMonorepo && (await Bun.file(bundledCli).exists())) {\n cliPath = bundledCli;\n }\n\n const args = [...commandArgs];\n if (isMonorepo) {\n // Polly's config lives under scripts/, and its Stryker config sits at the\n // monorepo root (checked separately by `polly check`).\n args.unshift(\"--config\", \"scripts/coverage.config.ts\", \"--no-mutate\");\n }\n\n const proc = Bun.spawn([\"bun\", cliPath, ...args], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Coverage check failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Test command - delegate to @fairfox/polly-test\n */\nasync function test() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/test/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/cli.ts`;\n const testCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", testCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Browser test command — bundles *.browser.{ts,tsx} files, serves them,\n * opens a Puppeteer page, and collects results. Consumers use this to\n * run tests written with @fairfox/polly/test/browser in a real browser.\n */\nasync function testBrowser() {\n const bundledCli = `${__dirname}/../tools/test/src/browser/run.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/browser/run.ts`;\n const browserCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", browserCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Browser tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Check command - run all quality checks in sequence\n */\nasync function check() {\n const checks = [\n { name: \"Type checking\", fn: typecheck },\n { name: \"Linting\", fn: lint },\n { name: \"Testing\", fn: test },\n { name: \"Building\", fn: build },\n { name: \"Verification\", fn: verify, optional: true },\n { name: \"Visualization\", fn: visualize, optional: true },\n ];\n\n for (const { name, fn, optional } of checks) {\n try {\n await fn();\n } catch (_error) {\n if (optional) {\n continue;\n }\n console.log(`\\n\\x1b[31m✗ ${name} failed\\x1b[0m\\n`);\n process.exit(1);\n }\n }\n}\n\n/**\n * Init command - delegate to @fairfox/polly-init\n */\nasync function init() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/init/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/init/src/cli.ts`;\n const initCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", initCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Initialization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Help command — prints usage from the docstring at the top of this file\n */\nfunction help() {\n console.log(`Polly CLI — multi-execution-context framework\n\nUsage:\n polly init [name] [--type=TYPE] Create a new project\n polly check Run all checks (typecheck, lint, test, build)\n polly build [options] Build the project\n polly dev Build with watch mode\n polly typecheck Type check your code\n polly lint [--fix] Lint your code\n polly format Format your code\n polly test [args] Run tests (bun test)\n polly test --tier=T | --all | --full | --list\n Run the multi-tier suite (unit → integration\n → browser → e2e → verify) with timed reports\n polly test:browser [dir] Run *.browser.{ts,tsx} in Puppeteer\n polly coverage [flags] Per-file coverage policy, orphan detection,\n and Stryker target validation (zero-config)\n polly verify [args] Run formal verification\n polly visualize [args] Generate architecture diagrams\n polly quality [args] Run quality conformance checks\n polly help Show this help\n\nOptions:\n --prod Build for production (minified)\n --config <path> Path to config file (default: polly.config.ts)\n --fix Auto-fix lint/format issues\n --type=TYPE Project type for init command (pwa, extension, websocket, generic)`);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n try {\n switch (command) {\n case \"init\":\n await init();\n break;\n case \"check\":\n await check();\n break;\n case \"build\":\n await build();\n break;\n case \"dev\":\n await dev();\n break;\n case \"typecheck\":\n await typecheck();\n break;\n case \"lint\":\n await lint();\n break;\n case \"format\":\n await format();\n break;\n case \"test\":\n if (isTierMode(commandArgs)) {\n await testTiers();\n } else {\n await test();\n }\n break;\n case \"test:browser\":\n await testBrowser();\n break;\n case \"coverage\":\n await coverage();\n break;\n case \"verify\":\n await verify();\n break;\n case \"visualize\":\n await visualize();\n break;\n case \"quality\":\n await quality();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n help();\n break;\n default:\n console.log(`❌ Unknown command: ${command}\\n`);\n help();\n process.exit(1);\n }\n } catch (error) {\n console.log(\"\\n❌ Command failed:\", error);\n process.exit(1);\n }\n}\n\nmain();\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9670061BCD90AC2664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* @fairfox/polly/test/coverage — consumer-facing `polly coverage`.
|
|
4
|
+
*
|
|
5
|
+
* Zero-config: run it in any Polly project and it reports per-file coverage,
|
|
6
|
+
* orphan source files, and (if a Stryker config is present) dead mutate/test
|
|
7
|
+
* globs. Add a `coverage.config.ts` with a `defaultThreshold` to turn the
|
|
8
|
+
* report into an enforced gate, and `exempt` entries to record which
|
|
9
|
+
* higher-tier test covers a unit-thin file.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* polly coverage # report (zero-config) or enforce (with config)
|
|
13
|
+
* polly coverage --strict-orphans # fail on source no unit test imports
|
|
14
|
+
* polly coverage --orphans # list the orphan files
|
|
15
|
+
* polly coverage --no-mutate # skip the Stryker target check
|
|
16
|
+
* polly coverage --config <path> # explicit coverage.config.ts
|
|
17
|
+
* bun test --coverage | polly coverage --stdin
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// tools/test/src/coverage-policy/enforce.ts
|
|
21
|
+
var exports_enforce = {};
|
|
22
|
+
__export(exports_enforce, {
|
|
23
|
+
runCoverage: () => runCoverage,
|
|
24
|
+
parseCoverageTable: () => parseCoverageTable,
|
|
25
|
+
hasFailure: () => hasFailure,
|
|
26
|
+
evaluateCoverage: () => evaluateCoverage,
|
|
27
|
+
enforceCoverage: () => enforceCoverage
|
|
28
|
+
});
|
|
29
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
30
|
+
import { join as join2, resolve as resolve2 } from "node:path";
|
|
31
|
+
import { Glob } from "bun";
|
|
32
|
+
function normalizePath(raw) {
|
|
33
|
+
return raw.replace(/^(?:\.\.\/)+/, "");
|
|
34
|
+
}
|
|
35
|
+
function parseCoverageTable(text, srcDir) {
|
|
36
|
+
const prefix = `${srcDir}/`;
|
|
37
|
+
const rows = [];
|
|
38
|
+
for (const line of text.split(`
|
|
39
|
+
`)) {
|
|
40
|
+
if (!line.includes("|"))
|
|
41
|
+
continue;
|
|
42
|
+
if (line.includes("All files") || line.includes("% Funcs"))
|
|
43
|
+
continue;
|
|
44
|
+
if (line.trim().startsWith("---"))
|
|
45
|
+
continue;
|
|
46
|
+
const cells = line.split("|").map((c) => c.trim());
|
|
47
|
+
if (cells.length < 3)
|
|
48
|
+
continue;
|
|
49
|
+
const file = normalizePath(cells[0] ?? "");
|
|
50
|
+
const funcs = Number(cells[1]);
|
|
51
|
+
const lines = Number(cells[2]);
|
|
52
|
+
if (!file.startsWith(prefix) || Number.isNaN(funcs) || Number.isNaN(lines))
|
|
53
|
+
continue;
|
|
54
|
+
rows.push({ file, funcs, lines });
|
|
55
|
+
}
|
|
56
|
+
return rows;
|
|
57
|
+
}
|
|
58
|
+
async function runCoverage(root, testCwd) {
|
|
59
|
+
const proc = Bun.spawn(["bun", "test", "--coverage"], {
|
|
60
|
+
cwd: join2(root, testCwd),
|
|
61
|
+
stdout: "pipe",
|
|
62
|
+
stderr: "pipe"
|
|
63
|
+
});
|
|
64
|
+
const [out, err] = await Promise.all([
|
|
65
|
+
new Response(proc.stdout).text(),
|
|
66
|
+
new Response(proc.stderr).text()
|
|
67
|
+
]);
|
|
68
|
+
await proc.exited;
|
|
69
|
+
if (proc.exitCode !== 0) {
|
|
70
|
+
throw new Error(`bun test --coverage exited ${proc.exitCode}
|
|
71
|
+
${err}`);
|
|
72
|
+
}
|
|
73
|
+
return `${out}
|
|
74
|
+
${err}`;
|
|
75
|
+
}
|
|
76
|
+
function evaluateRows(rows, config) {
|
|
77
|
+
const t = config.defaultThreshold;
|
|
78
|
+
const exempt = config.exempt ?? {};
|
|
79
|
+
const violations = [];
|
|
80
|
+
const staleExempts = [];
|
|
81
|
+
if (!t)
|
|
82
|
+
return { violations, staleExempts };
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
if (exempt[row.file]) {
|
|
85
|
+
if (row.lines >= t.lines && row.funcs >= t.funcs)
|
|
86
|
+
staleExempts.push(row.file);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (row.lines < t.lines) {
|
|
90
|
+
violations.push({ file: row.file, metric: "lines", observed: row.lines, required: t.lines });
|
|
91
|
+
}
|
|
92
|
+
if (row.funcs < t.funcs) {
|
|
93
|
+
violations.push({ file: row.file, metric: "funcs", observed: row.funcs, required: t.funcs });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { violations, staleExempts };
|
|
97
|
+
}
|
|
98
|
+
function validateExemptions(root, config) {
|
|
99
|
+
const missingExemptFiles = [];
|
|
100
|
+
const missingClaimedBy = [];
|
|
101
|
+
for (const [file, entry] of Object.entries(config.exempt ?? {})) {
|
|
102
|
+
if (!existsSync2(resolve2(root, file)))
|
|
103
|
+
missingExemptFiles.push(file);
|
|
104
|
+
const claimedBy = entry.claimedBy.trim();
|
|
105
|
+
if (!claimedBy.startsWith("n/a") && !existsSync2(resolve2(root, claimedBy))) {
|
|
106
|
+
missingClaimedBy.push({ file, claimedBy });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { missingExemptFiles, missingClaimedBy };
|
|
110
|
+
}
|
|
111
|
+
async function findOrphans(root, srcDir, covered, config) {
|
|
112
|
+
const exempt = config.exempt ?? {};
|
|
113
|
+
const orphans = [];
|
|
114
|
+
const glob = new Glob(`${srcDir}/**/*.{ts,tsx}`);
|
|
115
|
+
for await (const file of glob.scan({ cwd: root, onlyFiles: true })) {
|
|
116
|
+
if (file.endsWith(".d.ts") || /\.test\.tsx?$/.test(file) || file.includes("/__tests__/")) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (covered.has(file) || exempt[file])
|
|
120
|
+
continue;
|
|
121
|
+
orphans.push(file);
|
|
122
|
+
}
|
|
123
|
+
return orphans.sort();
|
|
124
|
+
}
|
|
125
|
+
async function evaluateCoverage(root, rows, config) {
|
|
126
|
+
const srcDir = config.srcDir ?? DEFAULT_SRC;
|
|
127
|
+
const { violations, staleExempts } = evaluateRows(rows, config);
|
|
128
|
+
const { missingExemptFiles, missingClaimedBy } = validateExemptions(root, config);
|
|
129
|
+
const orphans = await findOrphans(root, srcDir, new Set(rows.map((r) => r.file)), config);
|
|
130
|
+
return {
|
|
131
|
+
rowCount: rows.length,
|
|
132
|
+
violations,
|
|
133
|
+
staleExempts,
|
|
134
|
+
missingExemptFiles,
|
|
135
|
+
missingClaimedBy,
|
|
136
|
+
orphans,
|
|
137
|
+
enforced: config.defaultThreshold !== undefined
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function enforceCoverage(root, config, coverageText) {
|
|
141
|
+
const srcDir = config.srcDir ?? DEFAULT_SRC;
|
|
142
|
+
const text = coverageText ?? await runCoverage(root, config.testCwd ?? ".");
|
|
143
|
+
const rows = parseCoverageTable(text, srcDir);
|
|
144
|
+
return evaluateCoverage(root, rows, config);
|
|
145
|
+
}
|
|
146
|
+
function hasFailure(findings, strictOrphans) {
|
|
147
|
+
return findings.violations.length > 0 || findings.staleExempts.length > 0 || findings.missingExemptFiles.length > 0 || findings.missingClaimedBy.length > 0 || strictOrphans && findings.orphans.length > 0;
|
|
148
|
+
}
|
|
149
|
+
var DEFAULT_SRC = "src";
|
|
150
|
+
var init_enforce = () => {};
|
|
151
|
+
|
|
152
|
+
// tools/test/src/coverage-policy/discover.ts
|
|
153
|
+
import { existsSync } from "node:fs";
|
|
154
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
155
|
+
function isCoverageConfig(value) {
|
|
156
|
+
return typeof value === "object" && value !== null;
|
|
157
|
+
}
|
|
158
|
+
async function importConfig(path) {
|
|
159
|
+
const mod = await import(path);
|
|
160
|
+
const candidate = mod["config"] ?? mod["default"];
|
|
161
|
+
if (!isCoverageConfig(candidate)) {
|
|
162
|
+
throw new Error(`${path} must export \`config\` (a CoverageConfig object)`);
|
|
163
|
+
}
|
|
164
|
+
return candidate;
|
|
165
|
+
}
|
|
166
|
+
async function loadCoverageConfig(root, explicitPath) {
|
|
167
|
+
if (explicitPath) {
|
|
168
|
+
const abs = isAbsolute(explicitPath) ? explicitPath : resolve(root, explicitPath);
|
|
169
|
+
if (!existsSync(abs))
|
|
170
|
+
throw new Error(`coverage config not found: ${abs}`);
|
|
171
|
+
return { config: await importConfig(abs), source: abs };
|
|
172
|
+
}
|
|
173
|
+
for (const name of ["coverage.config.ts", "coverage.config.js"]) {
|
|
174
|
+
const abs = join(root, name);
|
|
175
|
+
if (existsSync(abs))
|
|
176
|
+
return { config: await importConfig(abs), source: abs };
|
|
177
|
+
}
|
|
178
|
+
return { config: {}, source: null };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// tools/test/src/coverage-policy/cli.ts
|
|
182
|
+
init_enforce();
|
|
183
|
+
|
|
184
|
+
// tools/test/src/coverage-policy/mutate-targets.ts
|
|
185
|
+
import { existsSync as existsSync3, readFileSync } from "node:fs";
|
|
186
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
187
|
+
import { Glob as Glob2 } from "bun";
|
|
188
|
+
async function findStrykerConfigs(root) {
|
|
189
|
+
const found = [];
|
|
190
|
+
const single = join3(root, "stryker.conf.json");
|
|
191
|
+
if (existsSync3(single))
|
|
192
|
+
found.push(single);
|
|
193
|
+
const glob = new Glob2("stryker/*.{json,conf.json}");
|
|
194
|
+
for await (const rel of glob.scan({ cwd: root, onlyFiles: true })) {
|
|
195
|
+
found.push(join3(root, rel));
|
|
196
|
+
}
|
|
197
|
+
return found.sort();
|
|
198
|
+
}
|
|
199
|
+
function isGlob(pattern) {
|
|
200
|
+
return pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
|
|
201
|
+
}
|
|
202
|
+
async function resolvesToFile(pattern, cwd) {
|
|
203
|
+
if (pattern.startsWith("!"))
|
|
204
|
+
return true;
|
|
205
|
+
if (!isGlob(pattern))
|
|
206
|
+
return existsSync3(resolve3(cwd, pattern));
|
|
207
|
+
const glob = new Glob2(pattern);
|
|
208
|
+
for await (const _ of glob.scan({ cwd, onlyFiles: true }))
|
|
209
|
+
return true;
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
async function checkField(configPath, field, patterns, cwd) {
|
|
213
|
+
const issues = [];
|
|
214
|
+
for (const pattern of patterns ?? []) {
|
|
215
|
+
if (!await resolvesToFile(pattern, cwd)) {
|
|
216
|
+
issues.push({ config: configPath, field, pattern });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return issues;
|
|
220
|
+
}
|
|
221
|
+
async function validateMutateTargets(root) {
|
|
222
|
+
const configs = await findStrykerConfigs(root);
|
|
223
|
+
const issues = [];
|
|
224
|
+
for (const configPath of configs) {
|
|
225
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
226
|
+
const testFiles = config.testFiles ?? config.bun?.testFiles;
|
|
227
|
+
issues.push(...await checkField(configPath, "mutate", config.mutate, root));
|
|
228
|
+
issues.push(...await checkField(configPath, "testFiles", testFiles, root));
|
|
229
|
+
}
|
|
230
|
+
return { configs, issues };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// tools/test/src/coverage-policy/cli.ts
|
|
234
|
+
function parseArgs(argv) {
|
|
235
|
+
const flag = (name) => argv.includes(name);
|
|
236
|
+
const argValue = (name) => {
|
|
237
|
+
const i = argv.indexOf(name);
|
|
238
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
239
|
+
};
|
|
240
|
+
const strictOrphans = flag("--strict-orphans");
|
|
241
|
+
return {
|
|
242
|
+
root: process.cwd(),
|
|
243
|
+
configPath: argValue("--config"),
|
|
244
|
+
strictOrphans,
|
|
245
|
+
listOrphans: strictOrphans || flag("--orphans"),
|
|
246
|
+
stdin: flag("--stdin"),
|
|
247
|
+
mutate: !flag("--no-mutate"),
|
|
248
|
+
help: flag("--help") || flag("-h")
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async function readStdin() {
|
|
252
|
+
const chunks = [];
|
|
253
|
+
for await (const chunk of Bun.stdin.stream())
|
|
254
|
+
chunks.push(new TextDecoder().decode(chunk));
|
|
255
|
+
return chunks.join("");
|
|
256
|
+
}
|
|
257
|
+
function reportPolicy(f) {
|
|
258
|
+
for (const m of f.missingExemptFiles) {
|
|
259
|
+
process.stderr.write(`❌ exempt source missing: ${m}
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
for (const { file, claimedBy } of f.missingClaimedBy) {
|
|
263
|
+
process.stderr.write(`❌ ${file} → claimedBy missing: ${claimedBy}
|
|
264
|
+
`);
|
|
265
|
+
}
|
|
266
|
+
for (const s of f.staleExempts) {
|
|
267
|
+
process.stderr.write(`❌ exempt file now meets the floor — promote it: ${s}
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
270
|
+
for (const v of f.violations) {
|
|
271
|
+
process.stderr.write(`❌ ${v.file} ${v.metric}=${v.observed.toFixed(2)}% (need ≥ ${v.required}%)
|
|
272
|
+
`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function reportOrphans(f, args) {
|
|
276
|
+
if (f.orphans.length === 0)
|
|
277
|
+
return;
|
|
278
|
+
process.stderr.write(`${args.strictOrphans ? "❌" : "⚠️ "} ${f.orphans.length} src file(s) no unit test imports
|
|
279
|
+
`);
|
|
280
|
+
if (args.listOrphans)
|
|
281
|
+
for (const o of f.orphans)
|
|
282
|
+
process.stderr.write(` ${o}
|
|
283
|
+
`);
|
|
284
|
+
else
|
|
285
|
+
process.stderr.write(` --orphans to list, --strict-orphans to fail
|
|
286
|
+
`);
|
|
287
|
+
}
|
|
288
|
+
function reportMutate(report) {
|
|
289
|
+
if (report.issues.length === 0)
|
|
290
|
+
return;
|
|
291
|
+
process.stderr.write(`❌ ${report.issues.length} Stryker target(s) resolve to no files:
|
|
292
|
+
`);
|
|
293
|
+
for (const i of report.issues) {
|
|
294
|
+
process.stderr.write(` ${i.config} [${i.field}] ${i.pattern}
|
|
295
|
+
`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function showHelp() {
|
|
299
|
+
process.stdout.write(`polly coverage — coverage policy, orphan detection, Stryker target validation
|
|
300
|
+
|
|
301
|
+
` + ` --strict-orphans fail on source no unit test imports
|
|
302
|
+
` + ` --orphans list orphan files
|
|
303
|
+
` + ` --no-mutate skip the Stryker mutate/testFiles check
|
|
304
|
+
` + ` --config <path> explicit coverage.config.ts
|
|
305
|
+
` + " --stdin read a `bun test --coverage` table from stdin\n");
|
|
306
|
+
}
|
|
307
|
+
async function main() {
|
|
308
|
+
const args = parseArgs(process.argv.slice(2));
|
|
309
|
+
if (args.help) {
|
|
310
|
+
showHelp();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const { config, source } = await loadCoverageConfig(args.root, args.configPath);
|
|
314
|
+
const srcDir = config.srcDir ?? "src";
|
|
315
|
+
const findings = args.stdin ? await Promise.resolve().then(() => (init_enforce(), exports_enforce)).then(async (m) => {
|
|
316
|
+
const rows = parseCoverageTable(await readStdin(), srcDir);
|
|
317
|
+
return m.evaluateCoverage(args.root, rows, config);
|
|
318
|
+
}) : await enforceCoverage(args.root, config);
|
|
319
|
+
reportPolicy(findings);
|
|
320
|
+
reportOrphans(findings, args);
|
|
321
|
+
let mutate = { configs: [], issues: [] };
|
|
322
|
+
if (args.mutate) {
|
|
323
|
+
mutate = await validateMutateTargets(args.root);
|
|
324
|
+
reportMutate(mutate);
|
|
325
|
+
}
|
|
326
|
+
const failed = hasFailure(findings, args.strictOrphans) || mutate.issues.length > 0;
|
|
327
|
+
if (!failed) {
|
|
328
|
+
const mode = findings.enforced ? `floor enforced` : "report-only (no defaultThreshold)";
|
|
329
|
+
const where = source ? "" : " — zero-config";
|
|
330
|
+
const orphanNote = findings.orphans.length ? `, ${findings.orphans.length} orphan` : "";
|
|
331
|
+
const mutateNote = mutate.configs.length ? `, ${mutate.configs.length} stryker config(s) ok` : "";
|
|
332
|
+
process.stdout.write(`✅ coverage ok — ${findings.rowCount} src files, ${mode}${where}${orphanNote}${mutateNote}
|
|
333
|
+
`);
|
|
334
|
+
}
|
|
335
|
+
process.exit(failed ? 1 : 0);
|
|
336
|
+
}
|
|
337
|
+
await main();
|
|
338
|
+
|
|
339
|
+
//# debugId=7B6414401C7DC44464756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../tools/test/src/coverage-policy/enforce.ts", "../tools/test/src/coverage-policy/discover.ts", "../tools/test/src/coverage-policy/cli.ts", "../tools/test/src/coverage-policy/mutate-targets.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
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
|
+
"/**\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",
|
|
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
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "7B6414401C7DC44464756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fairfox/polly/test/coverage — zero-config loading.
|
|
3
|
+
*
|
|
4
|
+
* `polly coverage` runs without any config: it looks for a `coverage.config.ts`
|
|
5
|
+
* (or `.js`) at the project root, and if there is none it returns an empty
|
|
6
|
+
* config, which the engine treats as report-only — it prints the numbers and
|
|
7
|
+
* the orphan count but never fails the build. A consumer opts into enforcement
|
|
8
|
+
* by adding a `defaultThreshold`, and into legible tiering by adding `exempt`
|
|
9
|
+
* entries. This mirrors how `polly test --tier` discovers tiers by convention.
|
|
10
|
+
*/
|
|
11
|
+
import type { CoverageConfig } from "./types";
|
|
12
|
+
export interface LoadedConfig {
|
|
13
|
+
config: CoverageConfig;
|
|
14
|
+
/** Absolute path the config was loaded from, or null for the zero-config default. */
|
|
15
|
+
source: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the coverage config. With `explicitPath` (Polly's own front-end
|
|
19
|
+
* passes scripts/coverage.config.ts) that file must exist. Otherwise look for
|
|
20
|
+
* coverage.config.{ts,js} at the root; absent → the zero-config report-only
|
|
21
|
+
* default.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadCoverageConfig(root: string, explicitPath?: string): Promise<LoadedConfig>;
|