start-command 0.37.9 → 0.39.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 +26 -0
- package/README.md +7 -2
- package/bunfig.toml +4 -0
- package/eslint.config.mjs +1 -0
- package/package.json +4 -4
- package/src/bin/cli.js +62 -26
- package/src/lib/args-parser-queries.js +45 -1
- package/src/lib/args-parser.js +19 -0
- package/src/lib/cgroup-memory.js +95 -23
- package/src/lib/command-builder.js +4 -0
- package/src/lib/container-removal.js +4 -5
- package/src/lib/detached-finalize.js +1 -0
- package/src/lib/docker-command-handoff.js +34 -6
- package/src/lib/docker-init-option.js +65 -0
- package/src/lib/docker-inspect-batch.js +176 -0
- package/src/lib/docker-resource-options.js +21 -2
- package/src/lib/docker-runtime-args.js +19 -3
- package/src/lib/execution-attempt.js +19 -9
- package/src/lib/execution-chain.js +182 -0
- package/src/lib/execution-control.js +8 -6
- package/src/lib/execution-env.js +120 -0
- package/src/lib/execution-identity.js +180 -0
- package/src/lib/execution-recovery.js +21 -1
- package/src/lib/execution-resume.js +21 -1
- package/src/lib/execution-store.js +30 -23
- package/src/lib/exit-evidence.js +139 -7
- package/src/lib/isolation.js +21 -3
- package/src/lib/launch-identity-options.js +81 -0
- package/src/lib/launch-identity.js +411 -0
- package/src/lib/log-uploader.js +15 -1
- package/src/lib/output-blocks.js +11 -0
- package/src/lib/query-commands.js +18 -3
- package/src/lib/resume-resources.js +2 -2
- package/src/lib/screen-isolation.js +11 -2
- package/src/lib/spawn-helpers.js +33 -16
- package/src/lib/status-formatter.js +39 -131
- package/src/lib/status-probe.js +149 -0
- package/src/lib/store-decode-cache.js +141 -0
- package/src/lib/usage.js +11 -1
- package/test/args-parser-attach-resume.js +1 -1
- package/test/args-parser-control.js +2 -2
- package/test/cargo-lock-version.mjs +121 -0
- package/test/check-rust-coverage.mjs +71 -0
- package/test/check-web-archive.mjs +154 -0
- package/test/ci-workflow-invariants.js +204 -0
- package/test/command-builder-docker-cleanup.js +24 -0
- package/test/command-exit-codes.mjs +91 -0
- package/test/docker-runtime-options.js +2 -0
- package/test/docker-snapshot-safety.js +39 -0
- package/test/execution-resume.js +2 -0
- package/test/fixtures/fake-npm.sh +64 -0
- package/test/fixtures/lychee-report.md +39 -0
- package/test/javascript-first-ci.js +69 -0
- package/test/links-workflow.mjs +127 -0
- package/test/lint-gate.mjs +56 -0
- package/test/npm-publish.mjs +181 -0
- package/test/recheck-broken-links.mjs +452 -0
- package/test/regression-176.js +2 -0
- package/test/regression-187.js +2 -0
- package/test/regression-191.js +4 -0
- package/test/regression-193.js +2 -0
- package/test/regression-198.js +2 -0
- package/test/regression-207.js +8 -2
- package/test/regression-224.js +2 -0
- package/test/regression-229.js +267 -0
- package/test/regression-230.js +234 -0
- package/test/regression-231.js +298 -0
- package/test/regression-236.js +250 -0
- package/test/regression-238.js +129 -0
- package/test/regression-241.js +254 -0
- package/test/regression-242.js +241 -0
- package/test/regression-243.js +493 -0
- package/test/regression-244.js +471 -0
- package/test/release-push-retry.mjs +172 -0
- package/test/run-js-tests.mjs +15 -3
- package/test/setup/private-snapshot-lock.js +17 -0
- package/test/shared-golden.js +44 -2
- package/test/transient-links.mjs +139 -0
package/test/shared-golden.js
CHANGED
|
@@ -9,6 +9,8 @@ const {
|
|
|
9
9
|
formatRecord,
|
|
10
10
|
enrichDetachedStatus,
|
|
11
11
|
} = require('../src/lib/status-formatter');
|
|
12
|
+
const { executionAliases } = require('../src/lib/execution-identity');
|
|
13
|
+
const { buildChain, identityTextLines } = require('../src/lib/execution-chain');
|
|
12
14
|
const fixtures = JSON.parse(
|
|
13
15
|
readFileSync(resolve(__dirname, '../../parity/fixtures/contracts.json'))
|
|
14
16
|
);
|
|
@@ -35,18 +37,45 @@ describe('shared JavaScript and Rust golden contracts', () => {
|
|
|
35
37
|
);
|
|
36
38
|
});
|
|
37
39
|
}
|
|
40
|
+
// The fixtures describe the defaults, so a host-wide docker init default
|
|
41
|
+
// (issue #244) must not leak into them.
|
|
42
|
+
function parseWithoutHostDefaults(argv) {
|
|
43
|
+
const saved = process.env.START_COMMAND_DOCKER_INIT;
|
|
44
|
+
delete process.env.START_COMMAND_DOCKER_INIT;
|
|
45
|
+
try {
|
|
46
|
+
return parseArgs(argv);
|
|
47
|
+
} finally {
|
|
48
|
+
if (saved !== undefined) {
|
|
49
|
+
process.env.START_COMMAND_DOCKER_INIT = saved;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
38
53
|
for (const fixture of fixtures['cli-parsing']) {
|
|
39
54
|
it(`CLI parsing: ${fixture.name}`, () => {
|
|
40
55
|
if (fixture.error) {
|
|
41
|
-
assert.throws(
|
|
56
|
+
assert.throws(
|
|
57
|
+
() => parseWithoutHostDefaults(fixture.argv),
|
|
58
|
+
new RegExp(fixture.error)
|
|
59
|
+
);
|
|
42
60
|
return;
|
|
43
61
|
}
|
|
44
|
-
const parsed =
|
|
62
|
+
const parsed = parseWithoutHostDefaults(fixture.argv);
|
|
45
63
|
assert.equal(parsed.command, fixture.command);
|
|
46
64
|
assert.deepEqual(parsed.rawCommand, fixture.rawCommand);
|
|
47
65
|
for (const [key, value] of Object.entries(fixture.options)) {
|
|
48
66
|
assert.deepEqual(parsed.wrapperOptions[key], value, key);
|
|
49
67
|
}
|
|
68
|
+
if (fixture.dockerResourceLimits) {
|
|
69
|
+
const {
|
|
70
|
+
resolveResourceOptions,
|
|
71
|
+
} = require('../src/lib/docker-resource-options');
|
|
72
|
+
assert.deepEqual(
|
|
73
|
+
buildDockerRuntimeArgs(
|
|
74
|
+
resolveResourceOptions(parsed.wrapperOptions)
|
|
75
|
+
).sort(),
|
|
76
|
+
[...fixture.dockerResourceLimits].sort()
|
|
77
|
+
);
|
|
78
|
+
}
|
|
50
79
|
});
|
|
51
80
|
}
|
|
52
81
|
for (const fixture of fixtures['isolation-arguments']) {
|
|
@@ -80,6 +109,19 @@ describe('shared JavaScript and Rust golden contracts', () => {
|
|
|
80
109
|
fixture.text
|
|
81
110
|
);
|
|
82
111
|
});
|
|
112
|
+
it('reports execution names and the continuation chain (issue #231)', () => {
|
|
113
|
+
const fixture = fixtures['status-identity'];
|
|
114
|
+
const records = fixture.records.map((r) => ExecutionRecord.fromObject(r));
|
|
115
|
+
const record = records.find((r) => r.uuid === fixture.uuid);
|
|
116
|
+
const aliases = executionAliases(record);
|
|
117
|
+
const chain = buildChain(records, record).map((r) => r.uuid);
|
|
118
|
+
assert.deepEqual(aliases, fixture.aliases);
|
|
119
|
+
assert.deepEqual(chain, fixture.chain);
|
|
120
|
+
assert.deepEqual(
|
|
121
|
+
identityTextLines({ aliases, continues: record.continues, chain }),
|
|
122
|
+
fixture.text
|
|
123
|
+
);
|
|
124
|
+
});
|
|
83
125
|
for (const fixture of fixtures['execution-record-format']) {
|
|
84
126
|
it(`preserves the complete execution record format (exit ${fixture.exitCode})`, () => {
|
|
85
127
|
assert.deepEqual(ExecutionRecord.fromObject(fixture).toObject(), fixture);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { describe, expect, it } from 'bun:test';
|
|
5
|
+
import {
|
|
6
|
+
parseLycheeFailures,
|
|
7
|
+
recheckUnanswered,
|
|
8
|
+
} from '../../scripts/recheck-broken-links.mjs';
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(
|
|
11
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
12
|
+
'../..'
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const url = 'https://github.com/owner/repo/blob/main/src/index.js';
|
|
16
|
+
const base = { accept: '200..=299', userAgent: 'lychee' };
|
|
17
|
+
|
|
18
|
+
describe('transient link failures', () => {
|
|
19
|
+
it('classifies cached 503 and rejected 429 as retryable, preserving 404', () => {
|
|
20
|
+
const failures = parseLycheeFailures(
|
|
21
|
+
[
|
|
22
|
+
`- [503] <${url}> | Error (cached)`,
|
|
23
|
+
`- [ERROR] <${url}> | Rejected status code: 429 Too Many Requests`,
|
|
24
|
+
`- [404] <${url}> | Not Found`,
|
|
25
|
+
].join('\n')
|
|
26
|
+
);
|
|
27
|
+
expect(failures.map((item) => item.retryable)).toEqual([true, true, false]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('backs off between transient answers and recovers only on success', async () => {
|
|
31
|
+
let now = 0;
|
|
32
|
+
const waits = [];
|
|
33
|
+
const statuses = [503, 429, 200];
|
|
34
|
+
const result = await recheckUnanswered([url], {
|
|
35
|
+
...base,
|
|
36
|
+
nowFn: () => now,
|
|
37
|
+
sleepFn: async (ms) => {
|
|
38
|
+
waits.push(ms);
|
|
39
|
+
now += ms;
|
|
40
|
+
},
|
|
41
|
+
fetchImpl: async () => ({ status: statuses.shift() }),
|
|
42
|
+
});
|
|
43
|
+
expect(result.recovered).toEqual([url]);
|
|
44
|
+
expect(waits).toEqual([5000, 10000]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('uses authenticated contents GET for GitHub blob and tree throttling', async () => {
|
|
48
|
+
for (const kind of ['blob', 'tree']) {
|
|
49
|
+
const page = `https://github.com/owner/repo/${kind}/release%2Fv1/src/a%20b.js`;
|
|
50
|
+
const calls = [];
|
|
51
|
+
const result = await recheckUnanswered([page], {
|
|
52
|
+
...base,
|
|
53
|
+
githubToken: 'test-token',
|
|
54
|
+
fetchImpl: async (target, init) => {
|
|
55
|
+
calls.push({ target, init });
|
|
56
|
+
return { status: calls.length === 1 ? 503 : 200 };
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
expect(result.recovered).toEqual([page]);
|
|
60
|
+
expect(calls[0].init.headers.authorization).toBe(undefined);
|
|
61
|
+
expect(calls[1].target).toBe(
|
|
62
|
+
'https://api.github.com/repos/owner/repo/contents/src/a%20b.js?ref=release%2Fv1'
|
|
63
|
+
);
|
|
64
|
+
expect(calls[1].init.method).toBe('GET');
|
|
65
|
+
expect(calls[1].init.headers.authorization).toBe('Bearer test-token');
|
|
66
|
+
expect(calls[1].init.redirect).toBe('error');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does not recover on an API denial and keeps credentials off unrelated hosts', async () => {
|
|
71
|
+
let now = 0;
|
|
72
|
+
const calls = [];
|
|
73
|
+
const result = await recheckUnanswered(
|
|
74
|
+
[url, 'https://other.example/page'],
|
|
75
|
+
{
|
|
76
|
+
...base,
|
|
77
|
+
githubToken: 'test-token',
|
|
78
|
+
budgetSeconds: 1,
|
|
79
|
+
nowFn: () => now,
|
|
80
|
+
fetchImpl: async (target, init) => {
|
|
81
|
+
calls.push({ target, init });
|
|
82
|
+
now += 100;
|
|
83
|
+
return {
|
|
84
|
+
status: target.startsWith('https://api.github.com/') ? 401 : 503,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
expect(result.recovered).toEqual([]);
|
|
90
|
+
expect(result.stillBroken.length).toBe(2);
|
|
91
|
+
const other = calls.find((call) =>
|
|
92
|
+
call.target.startsWith('https://other.example/')
|
|
93
|
+
);
|
|
94
|
+
expect(other.init.headers.authorization).toBe(undefined);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('never retries a definitive 404 or uses an API answer to hide it', async () => {
|
|
98
|
+
let calls = 0;
|
|
99
|
+
const result = await recheckUnanswered([url], {
|
|
100
|
+
...base,
|
|
101
|
+
githubToken: 'test-token',
|
|
102
|
+
fetchImpl: async () => {
|
|
103
|
+
calls++;
|
|
104
|
+
return { status: 404 };
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
expect(calls).toBe(1);
|
|
108
|
+
expect(result.recovered).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('bounds persistent 503 errors by the shared wall-clock budget', async () => {
|
|
112
|
+
let now = 0;
|
|
113
|
+
let calls = 0;
|
|
114
|
+
const result = await recheckUnanswered([url], {
|
|
115
|
+
...base,
|
|
116
|
+
budgetSeconds: 16,
|
|
117
|
+
nowFn: () => now,
|
|
118
|
+
sleepFn: async (ms) => {
|
|
119
|
+
now += ms;
|
|
120
|
+
},
|
|
121
|
+
fetchImpl: async () => {
|
|
122
|
+
calls++;
|
|
123
|
+
return { status: 503 };
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
expect(calls).toBe(3);
|
|
127
|
+
expect(result.recovered).toEqual([]);
|
|
128
|
+
expect(result.stillBroken.length).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('pins every Bun setup to the supported major line', () => {
|
|
132
|
+
const workflow = readFileSync(
|
|
133
|
+
path.join(root, '.github/workflows/js.yml'),
|
|
134
|
+
'utf8'
|
|
135
|
+
);
|
|
136
|
+
expect(workflow).not.toContain('bun-version: latest');
|
|
137
|
+
expect(workflow).toContain("bun-version: '1.x'");
|
|
138
|
+
});
|
|
139
|
+
});
|