start-command 0.36.0 → 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.
- package/CHANGELOG.md +8 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/src/bin/cli.js +5 -0
- package/src/lib/args-parser-queries.js +14 -0
- package/src/lib/args-parser.js +20 -1
- package/src/lib/command-builder.js +3 -0
- package/src/lib/docker-cleanup.js +12 -5
- package/src/lib/docker-command-handoff.js +75 -0
- package/src/lib/docker-labels.js +33 -0
- package/src/lib/docker-runtime-args.js +8 -0
- package/src/lib/docker-snapshot-safety.js +164 -0
- package/src/lib/execution-recovery.js +8 -1
- package/src/lib/execution-resume.js +184 -37
- package/src/lib/failure-handler.js +15 -5
- package/src/lib/isolation.js +20 -4
- package/src/lib/log-sanitizer.js +267 -0
- package/src/lib/log-uploader.js +38 -19
- package/src/lib/query-commands.js +4 -3
- package/src/lib/shell-utils.js +67 -17
- package/src/lib/usage.js +5 -1
- package/test/ci-workflow-invariants.js +15 -14
- package/test/docker-command-handoff.js +107 -0
- package/test/docker-runtime-options.js +4 -0
- package/test/docker-snapshot-safety.js +106 -0
- package/test/execution-resume.js +10 -8
- package/test/failure-handler.js +11 -0
- package/test/javascript-first-ci.js +216 -0
- package/test/log-sanitizer.js +306 -0
- package/test/regression-176.js +7 -0
- package/test/regression-187.js +9 -1
- package/test/regression-191.js +9 -1
- package/test/regression-193.js +11 -1
- package/test/regression-198.js +281 -0
- package/test/regression-199.js +79 -0
- package/test/regression-201.js +25 -0
- package/test/regression-202.js +92 -0
- package/test/regression-91.js +23 -20
- package/test/shared-golden.js +54 -0
- package/test/status-query.js +97 -15
package/test/status-query.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: ${
|
|
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
|
|