@magnusekdahl/parallix 1.2.1 → 1.3.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.
Files changed (79) hide show
  1. package/index.js +190 -185
  2. package/lib/commands/active.js +502 -579
  3. package/lib/commands/active.ts +665 -0
  4. package/lib/commands/checkpoint.js +98 -74
  5. package/lib/commands/checkpoint.ts +79 -0
  6. package/lib/commands/config.js +61 -31
  7. package/lib/commands/config.ts +47 -0
  8. package/lib/commands/coverage-gate.js +271 -246
  9. package/lib/commands/coverage-gate.ts +362 -0
  10. package/lib/commands/diff.js +140 -119
  11. package/lib/commands/diff.ts +125 -0
  12. package/lib/commands/draft.js +770 -923
  13. package/lib/commands/draft.ts +1021 -0
  14. package/lib/commands/handoff.js +588 -608
  15. package/lib/commands/handoff.ts +694 -0
  16. package/lib/commands/integrate.js +1331 -1468
  17. package/lib/commands/integrate.ts +1688 -0
  18. package/lib/commands/mission-start.js +279 -237
  19. package/lib/commands/mission-start.ts +263 -0
  20. package/lib/commands/rebase.js +606 -582
  21. package/lib/commands/rebase.ts +635 -0
  22. package/lib/commands/repair-handoff.js +181 -179
  23. package/lib/commands/repair-handoff.ts +228 -0
  24. package/lib/commands/resolve-conflict.js +121 -102
  25. package/lib/commands/resolve-conflict.ts +112 -0
  26. package/lib/commands/review.js +3 -4
  27. package/lib/commands/review.ts +14 -0
  28. package/lib/commands/setup-review.js +4 -5
  29. package/lib/commands/setup-review.ts +14 -0
  30. package/lib/commands/setup.js +3 -3
  31. package/lib/commands/setup.ts +2 -0
  32. package/lib/commands/stats-backfill.js +394 -359
  33. package/lib/commands/stats-backfill.ts +418 -0
  34. package/lib/commands/stats.js +1424 -1665
  35. package/lib/commands/stats.ts +2186 -0
  36. package/lib/commands/status.js +219 -200
  37. package/lib/commands/status.ts +219 -0
  38. package/lib/commands/verify.js +3 -1
  39. package/lib/commands/verify.ts +2 -0
  40. package/lib/core/nels.js +112 -127
  41. package/lib/core/nels.ts +189 -0
  42. package/lib/core/subagent-limit.js +15 -16
  43. package/lib/core/subagent-limit.ts +25 -0
  44. package/lib/index.js +142 -59
  45. package/lib/index.ts +134 -0
  46. package/lib/review/rebase.js +142 -145
  47. package/lib/review/rebase.ts +188 -0
  48. package/lib/review/review-adapter.js +99 -87
  49. package/lib/review/review-adapter.ts +198 -0
  50. package/lib/review/review-artifacts.js +486 -629
  51. package/lib/review/review-artifacts.ts +622 -0
  52. package/lib/review/review-commands.js +1220 -1373
  53. package/lib/review/review-commands.ts +1579 -0
  54. package/lib/review/review-events.js +544 -886
  55. package/lib/review/review-events.ts +954 -0
  56. package/lib/review/review-loop.js +945 -1020
  57. package/lib/review/review-loop.ts +1174 -0
  58. package/lib/review/review-polling.js +86 -115
  59. package/lib/review/review-polling.ts +194 -0
  60. package/lib/review/review-prompts.js +154 -189
  61. package/lib/review/review-prompts.ts +182 -0
  62. package/lib/review/review-state.js +246 -225
  63. package/lib/review/review-state.ts +353 -0
  64. package/lib/review/review.js +63 -84
  65. package/lib/review/review.ts +138 -0
  66. package/lib/tools/backlog.js +694 -680
  67. package/lib/tools/backlog.ts +835 -0
  68. package/lib/tools/forgejo.js +1270 -1405
  69. package/lib/tools/forgejo.ts +1793 -0
  70. package/lib/tools/gatekeeper.js +106 -96
  71. package/lib/tools/gatekeeper.ts +124 -0
  72. package/lib/tools/redgreen.js +200 -181
  73. package/lib/tools/redgreen.ts +221 -0
  74. package/lib/tools/sessions.js +58 -52
  75. package/lib/tools/sessions.ts +81 -0
  76. package/lib/tools/setup-review.js +902 -975
  77. package/lib/tools/setup-review.ts +1152 -0
  78. package/package.json +4 -4
  79. package/px.js +226 -208
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
-
2
+ "use strict";
3
3
  /**
4
4
  * coverage-gate.js - run parallix tests with Node's built-in coverage support
5
5
  * and enforce a configurable line-coverage threshold (default 90%).
@@ -47,122 +47,156 @@
47
47
  * Cleanup strategy: per-process inventory (PER_RUN_SCRATCH) + exit/SIGINT/SIGTERM
48
48
  * handlers (lines 131-141) that remove only tracked dirs. No blanket rm -rf.
49
49
  */
50
-
51
- const { spawnSync } = require('child_process');
52
- const fs = require('fs');
53
- const os = require('os');
54
- const path = require('path');
55
- const fmt = require('../core/fmt');
56
-
50
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
51
+ if (k2 === undefined) k2 = k;
52
+ var desc = Object.getOwnPropertyDescriptor(m, k);
53
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
54
+ desc = { enumerable: true, get: function() { return m[k]; } };
55
+ }
56
+ Object.defineProperty(o, k2, desc);
57
+ }) : (function(o, m, k, k2) {
58
+ if (k2 === undefined) k2 = k;
59
+ o[k2] = m[k];
60
+ }));
61
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
62
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
63
+ }) : function(o, v) {
64
+ o["default"] = v;
65
+ });
66
+ var __importStar = (this && this.__importStar) || (function () {
67
+ var ownKeys = function(o) {
68
+ ownKeys = Object.getOwnPropertyNames || function (o) {
69
+ var ar = [];
70
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
71
+ return ar;
72
+ };
73
+ return ownKeys(o);
74
+ };
75
+ return function (mod) {
76
+ if (mod && mod.__esModule) return mod;
77
+ var result = {};
78
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
79
+ __setModuleDefault(result, mod);
80
+ return result;
81
+ };
82
+ })();
83
+ const node_child_process_1 = require("node:child_process");
84
+ const fs = __importStar(require("node:fs"));
85
+ const os = __importStar(require("node:os"));
86
+ const path = __importStar(require("node:path"));
87
+ const fmt = __importStar(require("../core/fmt.js"));
57
88
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
58
89
  const SELF_TEST_FILE = 'coverage-gate.test.js';
59
90
  const TEMP_DIR_PREFIXES = [
60
- 'agents-',
61
- 'sessions-',
62
- 'review-',
63
- 'forgejo-',
64
- 'workflow-',
65
- 'gatekeeper-',
66
- 'graphify-',
67
- 'claude-',
68
- 'codex-',
69
- 'gemini-',
70
- 'opencode-',
71
- 'startagent-',
72
- 'test-',
73
- 'mission-task-',
74
- 'visualBoard-task-',
75
- 'backlog-'
91
+ 'agents-',
92
+ 'sessions-',
93
+ 'review-',
94
+ 'forgejo-',
95
+ 'workflow-',
96
+ 'gatekeeper-',
97
+ 'graphify-',
98
+ 'claude-',
99
+ 'codex-',
100
+ 'gemini-',
101
+ 'opencode-',
102
+ 'startagent-',
103
+ 'test-',
104
+ 'mission-task-',
105
+ 'visualBoard-task-',
106
+ 'backlog-'
76
107
  ];
77
108
  const COVERAGE_INCLUDES = [
78
- 'index.js',
79
- 'lib/**/*.js'
109
+ 'index.js',
110
+ 'lib/**/*.js'
80
111
  ];
81
112
  const COVERAGE_EXCLUDES = [
82
- 'test/**',
83
- 'prompts/**',
84
- 'config/*.json',
85
- '.workflow/**',
86
- 'node_modules/**'
113
+ 'test/**',
114
+ 'prompts/**',
115
+ 'config/*.json',
116
+ '.workflow/**',
117
+ 'node_modules/**'
87
118
  ];
88
119
  // The full suite regularly exceeds 10 minutes in this repository, especially
89
120
  // under cold caches. Keep the gate generous so it can finish without a manual
90
121
  // override while still failing on real hangs.
91
122
  const DEFAULT_TEST_TIMEOUT_MS = 3_600_000;
92
- /** @type {string[]} */
93
123
  const PER_RUN_SCRATCH = [];
94
124
  let threshold = 90;
95
125
  let dryRun = false;
96
126
  let lcov = false;
97
127
  let cleanupDone = false;
98
-
99
128
  for (let i = 2; i < process.argv.length; i++) {
100
- if (process.argv[i] === '--threshold' && process.argv[i + 1]) {
101
- threshold = parseFloat(process.argv[i + 1]);
102
- i++;
103
- } else if (process.argv[i] === '--dry-run') {
104
- dryRun = true;
105
- } else if (process.argv[i] === '--lcov') {
106
- lcov = true;
107
- }
129
+ if (process.argv[i] === '--threshold' && process.argv[i + 1]) {
130
+ threshold = parseFloat(process.argv[i + 1]);
131
+ i++;
132
+ }
133
+ else if (process.argv[i] === '--dry-run') {
134
+ dryRun = true;
135
+ }
136
+ else if (process.argv[i] === '--lcov') {
137
+ lcov = true;
138
+ }
108
139
  }
109
-
110
140
  function discoverTestFiles() {
111
- const testDir = path.join(REPO_ROOT, 'test');
112
- if (!fs.existsSync(testDir)) {return [];}
113
- return fs.readdirSync(testDir)
114
- .filter(file => file.endsWith('.test.js'))
115
- .filter(file => file !== SELF_TEST_FILE)
116
- .map(file => path.join(testDir, file))
117
- .sort();
141
+ const testDir = path.join(REPO_ROOT, 'test');
142
+ if (!fs.existsSync(testDir)) {
143
+ return [];
144
+ }
145
+ return fs.readdirSync(testDir)
146
+ .filter(file => file.endsWith('.test.js'))
147
+ .filter(file => file !== SELF_TEST_FILE)
148
+ .map(file => path.join(testDir, file))
149
+ .sort();
118
150
  }
119
-
120
151
  function listTempEntries(tmpRoot = os.tmpdir()) {
121
- if (!fs.existsSync(tmpRoot)) {return new Set();}
122
- return new Set(fs.readdirSync(tmpRoot));
152
+ if (!fs.existsSync(tmpRoot)) {
153
+ return new Set();
154
+ }
155
+ return new Set(fs.readdirSync(tmpRoot));
123
156
  }
124
-
125
- /** @param {string} name */
126
157
  function shouldCleanTempDir(name) {
127
- return TEMP_DIR_PREFIXES.some(prefix => name.startsWith(prefix));
158
+ return TEMP_DIR_PREFIXES.some(prefix => name.startsWith(prefix));
128
159
  }
129
-
130
- /** @param {Set<string>} beforeEntries @param {string} tmpRoot */
131
160
  function cleanupNewTempDirs(beforeEntries, tmpRoot = os.tmpdir()) {
132
- if (!fs.existsSync(tmpRoot)) {return;}
133
- for (const name of fs.readdirSync(tmpRoot)) {
134
- if (beforeEntries.has(name) || !shouldCleanTempDir(name)) {continue;}
135
- const fullPath = path.join(tmpRoot, name);
136
- let stat = null;
137
- try {
138
- stat = fs.statSync(fullPath);
139
- } catch (_) {
140
- continue;
161
+ if (!fs.existsSync(tmpRoot)) {
162
+ return;
163
+ }
164
+ for (const name of fs.readdirSync(tmpRoot)) {
165
+ if (beforeEntries.has(name) || !shouldCleanTempDir(name)) {
166
+ continue;
167
+ }
168
+ const fullPath = path.join(tmpRoot, name);
169
+ let stat = null;
170
+ try {
171
+ stat = fs.statSync(fullPath);
172
+ }
173
+ catch (_) {
174
+ continue;
175
+ }
176
+ if (!stat.isDirectory()) {
177
+ continue;
178
+ }
179
+ try {
180
+ fs.rmSync(fullPath, { recursive: true, force: true });
181
+ }
182
+ catch (_) { }
141
183
  }
142
- if (!stat.isDirectory()) {continue;}
143
- try {
144
- fs.rmSync(fullPath, { recursive: true, force: true });
145
- } catch (_) {}
146
- }
147
184
  }
148
-
149
185
  function createPerRunScratchDirs() {
150
- const coverageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'node-coverage-'));
151
- PER_RUN_SCRATCH.push(coverageDir);
152
- return coverageDir;
186
+ const coverageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'node-coverage-'));
187
+ PER_RUN_SCRATCH.push(coverageDir);
188
+ return coverageDir;
153
189
  }
154
-
155
190
  function createPerRunTmpRoot() {
156
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'coverage-gate-tmp-'));
157
- PER_RUN_SCRATCH.push(tmpRoot);
158
- return tmpRoot;
191
+ const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'coverage-gate-tmp-'));
192
+ PER_RUN_SCRATCH.push(tmpRoot);
193
+ return tmpRoot;
159
194
  }
160
-
161
195
  function createMockGraphifyBin() {
162
- const graphifyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'graphify-'));
163
- PER_RUN_SCRATCH.push(graphifyDir);
164
- const graphifyBin = path.join(graphifyDir, 'graphify');
165
- fs.writeFileSync(graphifyBin, `#!/usr/bin/env bash
196
+ const graphifyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'graphify-'));
197
+ PER_RUN_SCRATCH.push(graphifyDir);
198
+ const graphifyBin = path.join(graphifyDir, 'graphify');
199
+ fs.writeFileSync(graphifyBin, `#!/usr/bin/env bash
166
200
  set -euo pipefail
167
201
  if [[ "\${1:-}" == "--help" ]]; then
168
202
  echo "mock graphify"
@@ -178,188 +212,179 @@ fi
178
212
  echo "Unhandled graphify command: $*" >&2
179
213
  exit 1
180
214
  `, { mode: 0o755 });
181
- return graphifyBin;
215
+ return graphifyBin;
182
216
  }
183
-
184
217
  function cleanupPerRunScratch() {
185
- if (cleanupDone) {return;}
186
- cleanupDone = true;
187
- for (const dir of PER_RUN_SCRATCH) {
188
- try {
189
- if (fs.existsSync(dir)) {
190
- fs.rmSync(dir, { recursive: true, force: true });
191
- }
192
- } catch (_) {}
193
- }
194
- PER_RUN_SCRATCH.length = 0;
218
+ if (cleanupDone) {
219
+ return;
220
+ }
221
+ cleanupDone = true;
222
+ for (const dir of PER_RUN_SCRATCH) {
223
+ try {
224
+ if (fs.existsSync(dir)) {
225
+ fs.rmSync(dir, { recursive: true, force: true });
226
+ }
227
+ }
228
+ catch (_) { }
229
+ }
230
+ PER_RUN_SCRATCH.length = 0;
195
231
  }
196
-
197
232
  function registerExitHandlers() {
198
- process.on('exit', cleanupPerRunScratch);
199
- process.on('SIGINT', () => {
200
- cleanupPerRunScratch();
201
- process.exit(130);
202
- });
203
- process.on('SIGTERM', () => {
204
- cleanupPerRunScratch();
205
- process.exit(143);
206
- });
233
+ process.on('exit', cleanupPerRunScratch);
234
+ process.on('SIGINT', () => {
235
+ cleanupPerRunScratch();
236
+ process.exit(130);
237
+ });
238
+ process.on('SIGTERM', () => {
239
+ cleanupPerRunScratch();
240
+ process.exit(143);
241
+ });
207
242
  }
208
-
209
243
  function resetPerRunScratchState() {
210
- PER_RUN_SCRATCH.length = 0;
211
- cleanupDone = false;
244
+ PER_RUN_SCRATCH.length = 0;
245
+ cleanupDone = false;
212
246
  }
213
-
214
- /** @param {string[]} testFiles @param {number} coverageThreshold @param {boolean} useLcov */
215
247
  function buildCoverageArgs(testFiles, coverageThreshold = threshold, useLcov = lcov) {
216
- const args = [
217
- '--test',
218
- '--experimental-test-coverage',
219
- `--test-coverage-lines=${coverageThreshold}`,
220
- ...COVERAGE_INCLUDES.flatMap(pattern => ['--test-coverage-include', pattern]),
221
- ...COVERAGE_EXCLUDES.flatMap(pattern => ['--test-coverage-exclude', pattern]),
222
- ];
223
-
224
- if (useLcov) {
225
- const coverageDir = path.join(REPO_ROOT, 'coverage');
226
- if (!fs.existsSync(coverageDir)) {
227
- fs.mkdirSync(coverageDir, { recursive: true });
248
+ const args = [
249
+ '--test',
250
+ '--experimental-test-coverage',
251
+ `--test-coverage-lines=${coverageThreshold}`,
252
+ ...COVERAGE_INCLUDES.flatMap(pattern => ['--test-coverage-include', pattern]),
253
+ ...COVERAGE_EXCLUDES.flatMap(pattern => ['--test-coverage-exclude', pattern]),
254
+ ];
255
+ if (useLcov) {
256
+ const coverageDir = path.join(REPO_ROOT, 'coverage');
257
+ if (!fs.existsSync(coverageDir)) {
258
+ fs.mkdirSync(coverageDir, { recursive: true });
259
+ }
260
+ args.push('--test-reporter=lcov');
261
+ args.push(`--test-reporter-destination=${path.join(coverageDir, 'lcov.info')}`);
262
+ args.push('--test-reporter=spec');
263
+ args.push('--test-reporter-destination=stdout');
228
264
  }
229
- args.push('--test-reporter=lcov');
230
- args.push(`--test-reporter-destination=${path.join(coverageDir, 'lcov.info')}`);
231
- args.push('--test-reporter=spec');
232
- args.push('--test-reporter-destination=stdout');
233
- }
234
-
235
- args.push(...testFiles);
236
- return args;
265
+ args.push(...testFiles);
266
+ return args;
237
267
  }
238
-
239
268
  function resolveTestTimeoutMs(env = process.env) {
240
- const raw = env.WORKFLOW_COVERAGE_GATE_TIMEOUT_MS;
241
- if (raw === undefined || raw === '') {return DEFAULT_TEST_TIMEOUT_MS;}
242
- const parsed = Number(raw);
243
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TEST_TIMEOUT_MS;
269
+ const raw = env.WORKFLOW_COVERAGE_GATE_TIMEOUT_MS;
270
+ if (raw === undefined || raw === '') {
271
+ return DEFAULT_TEST_TIMEOUT_MS;
272
+ }
273
+ const parsed = Number(raw);
274
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TEST_TIMEOUT_MS;
244
275
  }
245
-
246
- /** @param {string[]} testFiles @param {number} coverageThreshold @param {Function} _spawnSync */
247
- function runTests(testFiles, coverageThreshold = threshold, _spawnSync = spawnSync) {
248
- const tmpRoot = createPerRunTmpRoot();
249
- const tmpEntriesBefore = listTempEntries(tmpRoot);
250
- const nodeCoverageDir = createPerRunScratchDirs();
251
- const childEnv = { ...process.env };
252
- delete childEnv.NODE_TEST_CONTEXT;
253
- delete childEnv.NODE_OPTIONS;
254
- childEnv.TMPDIR = tmpRoot;
255
- childEnv.NODE_V8_COVERAGE = nodeCoverageDir;
256
- childEnv.GRAPHIFY_BIN = createMockGraphifyBin();
257
-
258
- const result = _spawnSync(process.execPath, buildCoverageArgs(testFiles, coverageThreshold), {
259
- encoding: 'utf8',
260
- cwd: REPO_ROOT,
261
- env: childEnv,
262
- stdio: ['pipe', 'inherit', 'inherit'],
263
- timeout: resolveTestTimeoutMs(),
264
- });
265
-
266
- cleanupNewTempDirs(tmpEntriesBefore, tmpRoot);
267
- cleanupPerRunScratch();
268
-
269
- if (result.error) {
270
- fmt.log.error(`[coverage-gate] failed to spawn test runner: ${result.error.message}`);
271
- return 1;
272
- }
273
-
274
- if (result.signal) {
275
- fmt.log.error(`[coverage-gate] test runner killed by signal ${result.signal}`);
276
- return 1;
277
- }
278
-
279
- return result.status === null ? 1 : result.status;
276
+ function runTests(testFiles, coverageThreshold = threshold, _spawnSync = node_child_process_1.spawnSync) {
277
+ const tmpRoot = createPerRunTmpRoot();
278
+ const tmpEntriesBefore = listTempEntries(tmpRoot);
279
+ const nodeCoverageDir = createPerRunScratchDirs();
280
+ const childEnv = { ...process.env };
281
+ delete childEnv.NODE_TEST_CONTEXT;
282
+ delete childEnv.NODE_OPTIONS;
283
+ childEnv.TMPDIR = tmpRoot;
284
+ childEnv.NODE_V8_COVERAGE = nodeCoverageDir;
285
+ childEnv.GRAPHIFY_BIN = createMockGraphifyBin();
286
+ const result = _spawnSync(process.execPath, buildCoverageArgs(testFiles, coverageThreshold), {
287
+ encoding: 'utf8',
288
+ cwd: REPO_ROOT,
289
+ env: childEnv,
290
+ stdio: ['pipe', 'inherit', 'inherit'],
291
+ timeout: resolveTestTimeoutMs(),
292
+ });
293
+ cleanupNewTempDirs(tmpEntriesBefore, tmpRoot);
294
+ cleanupPerRunScratch();
295
+ if (result.error) {
296
+ fmt.log.error(`[coverage-gate] failed to spawn test runner: ${result.error.message}`);
297
+ return 1;
298
+ }
299
+ if (result.signal) {
300
+ fmt.log.error(`[coverage-gate] test runner killed by signal ${result.signal}`);
301
+ return 1;
302
+ }
303
+ return result.status === null ? 1 : result.status;
280
304
  }
281
-
282
305
  function main() {
283
- const testFiles = discoverTestFiles();
284
- if (testFiles.length === 0) {
285
- fmt.log.fail('no parallix test files found under test/');
286
- process.exit(1);
287
- }
288
-
289
- fmt.log.info(`Found ${testFiles.length} test file(s)`);
290
-
291
- if (dryRun) {
292
- fmt.log.info(`DRY-RUN mode — threshold=${threshold}%`);
293
- fmt.log.info('Denominator: index.js + lib/**/*.js');
294
- fmt.log.info(`Include globs: ${COVERAGE_INCLUDES.join(', ')}`);
295
- fmt.log.info(`Exclude globs: ${COVERAGE_EXCLUDES.join(', ')}`);
296
- fmt.log.info(`Would run: ${fmt.command(`${process.execPath} ${buildCoverageArgs(testFiles, threshold).join(' ')}`)}`);
297
- process.exit(0);
298
- }
299
-
300
- registerExitHandlers();
301
- process.exit(runTests(testFiles, threshold));
302
- }
303
-
304
- if (require.main === module) {
305
- main();
306
- }
307
-
308
- /** @param {string[]} args @param {{exitFn?: Function}} options */
309
- function run(args, options = {}) {
310
- /** @type{Function} */
311
- const exitFn = options.exitFn || process.exit;
312
- let threshold_ = 90;
313
- let dryRun_ = false;
314
- for (let i = 0; i < args.length; i++) {
315
- if (args[i] === '--threshold' && args[i + 1]) {
316
- threshold_ = parseFloat(args[i + 1]);
317
- i++;
318
- } else if (args[i] === '--dry-run') {
319
- dryRun_ = true;
306
+ const testFiles = discoverTestFiles();
307
+ if (testFiles.length === 0) {
308
+ fmt.log.fail('no parallix test files found under test/');
309
+ process.exit(1);
320
310
  }
321
- }
322
- const savedThreshold = threshold;
323
- const savedDryRun = dryRun;
324
- threshold = threshold_;
325
- dryRun = dryRun_;
326
- try {
327
- if (dryRun_) {
328
- const testFiles = discoverTestFiles();
329
- if (testFiles.length === 0) {
330
- if (typeof exitFn === 'function') {exitFn(1);}
331
- } else {
332
- fmt.log.info(`Found ${testFiles.length} test file(s)`);
311
+ fmt.log.info(`Found ${testFiles.length} test file(s)`);
312
+ if (dryRun) {
333
313
  fmt.log.info(`DRY-RUN mode — threshold=${threshold}%`);
334
314
  fmt.log.info('Denominator: index.js + lib/**/*.js');
335
315
  fmt.log.info(`Include globs: ${COVERAGE_INCLUDES.join(', ')}`);
336
316
  fmt.log.info(`Exclude globs: ${COVERAGE_EXCLUDES.join(', ')}`);
337
317
  fmt.log.info(`Would run: ${fmt.command(`${process.execPath} ${buildCoverageArgs(testFiles, threshold).join(' ')}`)}`);
338
- if (typeof exitFn === 'function') {exitFn(0);}
339
- else {process.exit(0);}
340
- }
341
- } else {
342
- registerExitHandlers();
343
- exitFn(runTests(discoverTestFiles(), threshold));
318
+ process.exit(0);
319
+ }
320
+ registerExitHandlers();
321
+ process.exit(runTests(testFiles, threshold));
322
+ }
323
+ if (typeof require !== 'undefined' && require.main === module) {
324
+ main();
325
+ }
326
+ function run(args, options = {}) {
327
+ const exitFn = options.exitFn || process.exit;
328
+ let threshold_ = 90;
329
+ let dryRun_ = false;
330
+ for (let i = 0; i < args.length; i++) {
331
+ if (args[i] === '--threshold' && args[i + 1]) {
332
+ threshold_ = parseFloat(args[i + 1]);
333
+ i++;
334
+ }
335
+ else if (args[i] === '--dry-run') {
336
+ dryRun_ = true;
337
+ }
338
+ }
339
+ const savedThreshold = threshold;
340
+ const savedDryRun = dryRun;
341
+ threshold = threshold_;
342
+ dryRun = dryRun_;
343
+ try {
344
+ if (dryRun_) {
345
+ const testFiles = discoverTestFiles();
346
+ if (testFiles.length === 0) {
347
+ if (typeof exitFn === 'function') {
348
+ exitFn(1);
349
+ }
350
+ }
351
+ else {
352
+ fmt.log.info(`Found ${testFiles.length} test file(s)`);
353
+ fmt.log.info(`DRY-RUN mode — threshold=${threshold}%`);
354
+ fmt.log.info('Denominator: index.js + lib/**/*.js');
355
+ fmt.log.info(`Include globs: ${COVERAGE_INCLUDES.join(', ')}`);
356
+ fmt.log.info(`Exclude globs: ${COVERAGE_EXCLUDES.join(', ')}`);
357
+ fmt.log.info(`Would run: ${fmt.command(`${process.execPath} ${buildCoverageArgs(testFiles, threshold).join(' ')}`)}`);
358
+ if (typeof exitFn === 'function') {
359
+ exitFn(0);
360
+ }
361
+ else {
362
+ process.exit(0);
363
+ }
364
+ }
365
+ }
366
+ else {
367
+ registerExitHandlers();
368
+ exitFn(runTests(discoverTestFiles(), threshold));
369
+ }
370
+ }
371
+ finally {
372
+ threshold = savedThreshold;
373
+ dryRun = savedDryRun;
344
374
  }
345
- } finally {
346
- threshold = savedThreshold;
347
- dryRun = savedDryRun;
348
- }
349
375
  }
350
-
376
+ run.buildCoverageArgs = buildCoverageArgs;
377
+ run.cleanupNewTempDirs = cleanupNewTempDirs;
378
+ run.cleanupPerRunScratch = cleanupPerRunScratch;
379
+ run.createPerRunScratchDirs = createPerRunScratchDirs;
380
+ run.COVERAGE_EXCLUDES = COVERAGE_EXCLUDES;
381
+ run.COVERAGE_INCLUDES = COVERAGE_INCLUDES;
382
+ run.DEFAULT_TEST_TIMEOUT_MS = DEFAULT_TEST_TIMEOUT_MS;
383
+ run.discoverTestFiles = discoverTestFiles;
384
+ run.listTempEntries = listTempEntries;
385
+ run.registerExitHandlers = registerExitHandlers;
386
+ run.resetPerRunScratchState = resetPerRunScratchState;
387
+ run.resolveTestTimeoutMs = resolveTestTimeoutMs;
388
+ run.runTests = runTests;
389
+ run.shouldCleanTempDir = shouldCleanTempDir;
351
390
  module.exports = run;
352
- module.exports.buildCoverageArgs = buildCoverageArgs;
353
- module.exports.cleanupNewTempDirs = cleanupNewTempDirs;
354
- module.exports.cleanupPerRunScratch = cleanupPerRunScratch;
355
- module.exports.createPerRunScratchDirs = createPerRunScratchDirs;
356
- module.exports.COVERAGE_EXCLUDES = COVERAGE_EXCLUDES;
357
- module.exports.COVERAGE_INCLUDES = COVERAGE_INCLUDES;
358
- module.exports.DEFAULT_TEST_TIMEOUT_MS = DEFAULT_TEST_TIMEOUT_MS;
359
- module.exports.discoverTestFiles = discoverTestFiles;
360
- module.exports.listTempEntries = listTempEntries;
361
- module.exports.registerExitHandlers = registerExitHandlers;
362
- module.exports.resetPerRunScratchState = resetPerRunScratchState;
363
- module.exports.resolveTestTimeoutMs = resolveTestTimeoutMs;
364
- module.exports.runTests = runTests;
365
- module.exports.shouldCleanTempDir = shouldCleanTempDir;