start-command 0.35.4 → 0.37.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 (67) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +16 -0
  3. package/package.json +1 -1
  4. package/src/bin/cli.js +48 -7
  5. package/src/lib/args-parser-queries.js +14 -0
  6. package/src/lib/args-parser.js +32 -1
  7. package/src/lib/atomic-write.js +34 -0
  8. package/src/lib/attached-diagnostics.js +90 -0
  9. package/src/lib/cgroup-memory.js +43 -11
  10. package/src/lib/command-builder.js +43 -0
  11. package/src/lib/cpu-penalty-monitor.js +226 -0
  12. package/src/lib/cpu-penalty.js +175 -0
  13. package/src/lib/detached-finalize.js +16 -0
  14. package/src/lib/docker-cleanup.js +35 -7
  15. package/src/lib/docker-command-handoff.js +75 -0
  16. package/src/lib/docker-labels.js +33 -0
  17. package/src/lib/docker-resource-limits.js +28 -0
  18. package/src/lib/docker-resource-options.js +180 -0
  19. package/src/lib/docker-runtime-args.js +23 -0
  20. package/src/lib/docker-snapshot-safety.js +164 -0
  21. package/src/lib/execution-recovery.js +153 -30
  22. package/src/lib/execution-resume-all.js +11 -0
  23. package/src/lib/execution-resume.js +314 -54
  24. package/src/lib/execution-store.js +25 -121
  25. package/src/lib/exit-evidence.js +123 -0
  26. package/src/lib/exit-reason.js +24 -6
  27. package/src/lib/failure-handler.js +15 -5
  28. package/src/lib/isolation.js +48 -7
  29. package/src/lib/launch-owner.js +25 -0
  30. package/src/lib/launch-persistence.js +54 -0
  31. package/src/lib/log-sanitizer.js +267 -0
  32. package/src/lib/log-uploader.js +38 -19
  33. package/src/lib/query-commands.js +18 -6
  34. package/src/lib/resume-resources.js +88 -0
  35. package/src/lib/shell-utils.js +67 -17
  36. package/src/lib/status-formatter.js +28 -0
  37. package/src/lib/store-lock.js +133 -0
  38. package/src/lib/usage.js +16 -1
  39. package/test/ci-workflow-invariants.js +15 -14
  40. package/test/docker-command-handoff.js +107 -0
  41. package/test/docker-runtime-options.js +10 -0
  42. package/test/docker-snapshot-safety.js +106 -0
  43. package/test/execution-resume.js +19 -11
  44. package/test/exit-reason.js +6 -1
  45. package/test/failure-handler.js +11 -0
  46. package/test/isolation.js +5 -4
  47. package/test/javascript-first-ci.js +216 -0
  48. package/test/log-sanitizer.js +306 -0
  49. package/test/regression-174.js +3 -1
  50. package/test/regression-176.js +7 -0
  51. package/test/regression-180.js +17 -10
  52. package/test/regression-182.js +7 -3
  53. package/test/regression-185.js +4 -9
  54. package/test/regression-187.js +11 -2
  55. package/test/regression-189.js +224 -0
  56. package/test/regression-190.js +111 -0
  57. package/test/regression-191.js +160 -0
  58. package/test/regression-192.js +66 -0
  59. package/test/regression-193.js +251 -0
  60. package/test/regression-194.js +182 -0
  61. package/test/regression-198.js +281 -0
  62. package/test/regression-199.js +79 -0
  63. package/test/regression-201.js +25 -0
  64. package/test/regression-202.js +92 -0
  65. package/test/regression-91.js +23 -20
  66. package/test/shared-golden.js +54 -0
  67. package/test/status-query.js +97 -15
@@ -53,19 +53,35 @@ function createExecutable(filePath, content) {
53
53
  fs.chmodSync(filePath, 0o755);
54
54
  }
55
55
 
56
+ function fakeUploaderScript(fakeBin, outputPrefix) {
57
+ const fixturePath = path.join(fakeBin, 'capture-upload.js');
58
+ fs.writeFileSync(
59
+ fixturePath,
60
+ [
61
+ "const fs = require('fs');",
62
+ 'const args = process.argv.slice(2);',
63
+ 'fs.writeFileSync(process.env.UPLOAD_CAPTURE, JSON.stringify({',
64
+ ' args, content: fs.readFileSync(args[0], "utf8"),',
65
+ ' mode: fs.statSync(args[0]).mode & 0o777,',
66
+ '}));',
67
+ `console.log(${JSON.stringify(outputPrefix)} + ': ' + args[0]);`,
68
+ '',
69
+ ].join('\n')
70
+ );
71
+ if (process.platform === 'win32') {
72
+ return `@echo off\r\n"${process.execPath}" "${fixturePath}" %*\r\n`;
73
+ }
74
+ return `#!/bin/sh\n"${process.execPath}" "${fixturePath}" "$@"\n`;
75
+ }
76
+
56
77
  function createFakeUploader(fakeBin, outputPrefix) {
78
+ const content = fakeUploaderScript(fakeBin, outputPrefix);
57
79
  if (process.platform === 'win32') {
58
- createExecutable(
59
- path.join(fakeBin, 'gh-upload-log.cmd'),
60
- `@echo off\r\necho ${outputPrefix}: %1\r\n`
61
- );
80
+ createExecutable(path.join(fakeBin, 'gh-upload-log.cmd'), content);
62
81
  return;
63
82
  }
64
83
 
65
- createExecutable(
66
- path.join(fakeBin, 'gh-upload-log'),
67
- `#!/bin/sh\necho "${outputPrefix}: $1"\n`
68
- );
84
+ createExecutable(path.join(fakeBin, 'gh-upload-log'), content);
69
85
  }
70
86
 
71
87
  describe('--status query functionality', () => {
@@ -262,10 +278,14 @@ describe('--status query functionality', () => {
262
278
  });
263
279
 
264
280
  describe('--upload-log functionality', () => {
265
- it('should run gh-upload-log with the stored execution log path', () => {
281
+ it('should upload a private sanitized copy and preserve the stored log', () => {
266
282
  const fakeBin = fs.mkdtempSync(path.join(os.tmpdir(), 'upload-log-bin-'));
267
283
  const logPath = path.join(TEST_APP_FOLDER, 'command.log');
268
- fs.writeFileSync(logPath, 'captured command output\n', 'utf8');
284
+ const capturePath = path.join(fakeBin, 'capture.json');
285
+ const token = ['gh', 'p_', 'a'.repeat(36)].join('');
286
+ const customSecret = 'fake-custom-credential';
287
+ const original = `captured command output\n${token}\n${customSecret}\n`;
288
+ fs.writeFileSync(logPath, original, 'utf8');
269
289
  createFakeUploader(fakeBin, 'fake uploader received');
270
290
 
271
291
  testRecord.logPath = logPath;
@@ -274,11 +294,28 @@ describe('--status query functionality', () => {
274
294
  const result = runCli(['--upload-log', testRecord.uuid], {
275
295
  PATH: fakeBin,
276
296
  HOME: fakeBin,
297
+ UPLOAD_CAPTURE: capturePath,
298
+ EXAMPLE_API_KEY: customSecret,
299
+ GH_UPLOAD_PUBLIC: 'true',
277
300
  });
278
301
 
279
302
  expect(result.exitCode).toBe(0);
280
- expect(result.stdout).toContain(`fake uploader received: ${logPath}`);
303
+ const captured = JSON.parse(fs.readFileSync(capturePath, 'utf8'));
304
+ expect(captured.args[0]).not.toBe(logPath);
305
+ expect(captured.args[1]).toBe('--private');
306
+ expect(captured.content).toContain('captured command output');
307
+ expect(captured.content).toContain('[REDACTED]');
308
+ expect(captured.content).not.toContain(token);
309
+ expect(captured.content).not.toContain(customSecret);
310
+ if (process.platform !== 'win32') {
311
+ expect(captured.mode).toBe(0o600);
312
+ }
313
+ expect(result.stdout).toContain(
314
+ `fake uploader received: ${captured.args[0]}`
315
+ );
281
316
  expect(result.stderr).toBe('');
317
+ expect(fs.readFileSync(logPath, 'utf8')).toBe(original);
318
+ expect(fs.existsSync(captured.args[0])).toBe(false);
282
319
 
283
320
  fs.rmSync(fakeBin, { recursive: true, force: true });
284
321
  });
@@ -293,8 +330,15 @@ describe('--status query functionality', () => {
293
330
  path.join(os.tmpdir(), 'upload-log-install-bin-')
294
331
  );
295
332
  const installMarker = path.join(fakeBin, 'install.log');
333
+ const capturePath = path.join(fakeBin, 'capture.json');
296
334
  const logPath = path.join(TEST_APP_FOLDER, 'install-command.log');
297
- fs.writeFileSync(logPath, 'captured command output\n', 'utf8');
335
+ const token = ['gh', 'p_', 'b'.repeat(36)].join('');
336
+ const original = `captured command output\n${token}\n`;
337
+ fs.writeFileSync(logPath, original, 'utf8');
338
+ const uploaderScript = fakeUploaderScript(
339
+ fakeBin,
340
+ 'installed uploader received'
341
+ );
298
342
 
299
343
  createExecutable(
300
344
  path.join(fakeBin, 'bun'),
@@ -302,8 +346,7 @@ describe('--status query functionality', () => {
302
346
  '#!/bin/sh',
303
347
  `echo "$@" > "${installMarker}"`,
304
348
  `cat > "${path.join(fakeBin, 'gh-upload-log')}" <<'SCRIPT'`,
305
- '#!/bin/sh',
306
- 'echo "installed uploader received: $1"',
349
+ uploaderScript,
307
350
  'SCRIPT',
308
351
  `chmod +x "${path.join(fakeBin, 'gh-upload-log')}"`,
309
352
  'exit 0',
@@ -317,6 +360,7 @@ describe('--status query functionality', () => {
317
360
  const result = runCli(['--upload-log', testRecord.uuid], {
318
361
  PATH: `${fakeBin}${path.delimiter}/usr/bin${path.delimiter}/bin`,
319
362
  HOME: fakeBin,
363
+ UPLOAD_CAPTURE: capturePath,
320
364
  });
321
365
 
322
366
  expect(result.exitCode).toBe(0);
@@ -324,10 +368,48 @@ describe('--status query functionality', () => {
324
368
  'install -g gh-upload-log'
325
369
  );
326
370
  expect(result.stdout).toContain('gh-upload-log not found');
371
+ const captured = JSON.parse(fs.readFileSync(capturePath, 'utf8'));
372
+ expect(captured.args[0]).not.toBe(logPath);
373
+ expect(captured.args[1]).toBe('--private');
374
+ expect(captured.content).toContain('captured command output');
375
+ expect(captured.content).toContain('[REDACTED]');
376
+ expect(captured.content).not.toContain(token);
377
+ expect(captured.mode).toBe(0o600);
327
378
  expect(result.stdout).toContain(
328
- `installed uploader received: ${logPath}`
379
+ `installed uploader received: ${captured.args[0]}`
380
+ );
381
+ expect(fs.readFileSync(logPath, 'utf8')).toBe(original);
382
+ expect(fs.existsSync(captured.args[0])).toBe(false);
383
+
384
+ fs.rmSync(fakeBin, { recursive: true, force: true });
385
+ });
386
+
387
+ it('should upload the original privately only with explicit --no-sanitize', () => {
388
+ const fakeBin = fs.mkdtempSync(path.join(os.tmpdir(), 'upload-raw-bin-'));
389
+ const capturePath = path.join(fakeBin, 'capture.json');
390
+ const logPath = path.join(TEST_APP_FOLDER, 'raw-command.log');
391
+ const token = ['gh', 'p_', 'c'.repeat(36)].join('');
392
+ const original = `captured command output\n${token}\n`;
393
+ fs.writeFileSync(logPath, original, 'utf8');
394
+ createFakeUploader(fakeBin, 'raw uploader received');
395
+ testRecord.logPath = logPath;
396
+ store.save(testRecord);
397
+
398
+ const result = runCli(
399
+ ['--upload-log', testRecord.uuid, '--no-sanitize'],
400
+ {
401
+ PATH: fakeBin,
402
+ HOME: fakeBin,
403
+ UPLOAD_CAPTURE: capturePath,
404
+ GH_UPLOAD_PUBLIC: 'true',
405
+ }
329
406
  );
330
407
 
408
+ expect(result.exitCode).toBe(0);
409
+ const captured = JSON.parse(fs.readFileSync(capturePath, 'utf8'));
410
+ expect(captured.args).toEqual([logPath, '--private']);
411
+ expect(captured.content).toBe(original);
412
+ expect(fs.readFileSync(logPath, 'utf8')).toBe(original);
331
413
  fs.rmSync(fakeBin, { recursive: true, force: true });
332
414
  });
333
415