start-command 0.37.7 → 0.37.9
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 +16 -0
- package/README.md +5 -0
- package/package.json +1 -1
- package/src/bin/cli.js +15 -0
- package/src/lib/args-parser-queries.js +45 -8
- package/src/lib/args-parser.js +10 -1
- package/src/lib/container-removal.js +163 -0
- package/src/lib/detached-finalize.js +29 -2
- package/src/lib/docker-cleanup.js +7 -4
- package/src/lib/docker-post-mortem.js +2 -0
- package/src/lib/docker-snapshot-resume.js +222 -0
- package/src/lib/docker-snapshot-safety.js +124 -12
- package/src/lib/execution-attempt.js +2 -0
- package/src/lib/execution-cleanup.js +193 -0
- package/src/lib/execution-control.js +2 -1
- package/src/lib/execution-recovery.js +2 -0
- package/src/lib/execution-resume.js +60 -6
- package/src/lib/execution-store.js +52 -0
- package/src/lib/isolation.js +4 -0
- package/src/lib/query-commands.js +16 -3
- package/src/lib/snapshot-resume-options.js +174 -0
- package/src/lib/status-formatter.js +59 -34
- package/src/lib/status-links-notation.js +97 -0
- package/src/lib/store-lock.js +3 -0
- package/src/lib/usage.js +7 -0
- package/test/regression-222.js +61 -0
- package/test/regression-224.js +807 -0
- package/test/regression-225.js +415 -0
- package/test/regression-226.js +81 -0
- package/test/shared-golden.js +20 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Links-notation and text rendering of nested `--status` values.
|
|
3
|
+
*
|
|
4
|
+
* `--status` stores structured option values such as `launchOwner`,
|
|
5
|
+
* `exitEvidence`, `resolvedLimits` and `resourceLimitSpecs`. They used to be
|
|
6
|
+
* passed through `escapeForLinksNotation` (and a template string in the text
|
|
7
|
+
* format), which printed `"[object Object]"` and lost the values (issue #226).
|
|
8
|
+
* These helpers render nested objects and arrays the same way the Rust
|
|
9
|
+
* formatter's `append_links_value` does, so both implementations print the
|
|
10
|
+
* same layout.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { escapeForLinksNotation } = require('./output-blocks');
|
|
14
|
+
|
|
15
|
+
function isPresent(value) {
|
|
16
|
+
return value !== null && value !== undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Append an array as a `(` … `)` block.
|
|
21
|
+
* @param {string[]} lines - Output lines
|
|
22
|
+
* @param {Array} values - Array to render
|
|
23
|
+
* @param {number} indent - Indentation of the parentheses
|
|
24
|
+
*/
|
|
25
|
+
function appendLinksArray(lines, values, indent) {
|
|
26
|
+
const prefix = ' '.repeat(indent);
|
|
27
|
+
if (values.length === 0) {
|
|
28
|
+
lines.push(`${prefix}()`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
lines.push(`${prefix}(`);
|
|
32
|
+
for (const value of values) {
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
appendLinksArray(lines, value, indent + 2);
|
|
35
|
+
} else if (isPresent(value) && typeof value === 'object') {
|
|
36
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
37
|
+
if (isPresent(childValue)) {
|
|
38
|
+
appendLinksValue(lines, childKey, childValue, indent + 2);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
lines.push(`${' '.repeat(indent + 2)}${escapeForLinksNotation(value)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push(`${prefix})`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Append `key value`, or `key` followed by its nested children.
|
|
50
|
+
* Children of an object are indented by four more spaces, matching the
|
|
51
|
+
* top-level ` key` / ` child` layout `--status` has always used.
|
|
52
|
+
* @param {string[]} lines - Output lines
|
|
53
|
+
* @param {string} key - Field name
|
|
54
|
+
* @param {*} value - Field value
|
|
55
|
+
* @param {number} indent - Indentation of the key
|
|
56
|
+
*/
|
|
57
|
+
function appendLinksValue(lines, key, value, indent) {
|
|
58
|
+
const prefix = ' '.repeat(indent);
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
lines.push(`${prefix}${key}`);
|
|
61
|
+
appendLinksArray(lines, value, indent + 2);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (isPresent(value) && typeof value === 'object') {
|
|
65
|
+
const entries = Object.entries(value);
|
|
66
|
+
if (entries.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
lines.push(`${prefix}${key}`);
|
|
70
|
+
for (const [childKey, childValue] of entries) {
|
|
71
|
+
if (isPresent(childValue)) {
|
|
72
|
+
appendLinksValue(lines, childKey, childValue, indent + 4);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
lines.push(`${prefix}${key} ${escapeForLinksNotation(value)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render an option value for the text format: scalars as-is, structured
|
|
82
|
+
* values as compact JSON (the Rust formatter uses `serde_json::to_string`).
|
|
83
|
+
* @param {*} value - Option value
|
|
84
|
+
* @returns {string} Rendered value
|
|
85
|
+
*/
|
|
86
|
+
function formatTextOptionValue(value) {
|
|
87
|
+
if (isPresent(value) && typeof value === 'object') {
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
appendLinksArray,
|
|
95
|
+
appendLinksValue,
|
|
96
|
+
formatTextOptionValue,
|
|
97
|
+
};
|
package/src/lib/store-lock.js
CHANGED
|
@@ -14,6 +14,8 @@ class LockManager {
|
|
|
14
14
|
this.identity = null;
|
|
15
15
|
this.token = null;
|
|
16
16
|
this.monitorOwner = monitorOwner;
|
|
17
|
+
// Extra fields published in the lock file, for example who holds it.
|
|
18
|
+
this.metadata = {};
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
acquire(timeout = LOCK_TIMEOUT_MS) {
|
|
@@ -44,6 +46,7 @@ class LockManager {
|
|
|
44
46
|
this.fs.writeFileSync(
|
|
45
47
|
temp,
|
|
46
48
|
JSON.stringify({
|
|
49
|
+
...this.metadata,
|
|
47
50
|
pid: process.pid,
|
|
48
51
|
timestamp: Date.now(),
|
|
49
52
|
hostname: os.hostname(),
|
package/src/lib/usage.js
CHANGED
|
@@ -54,9 +54,16 @@ Options:
|
|
|
54
54
|
--resume <id> Restart a stopped detached execution in the same environment
|
|
55
55
|
(append -- <command> to run a different command there)
|
|
56
56
|
--remove-original With legacy snapshot resume, remove the stopped original after launch
|
|
57
|
+
--remove-original-after <s> Remove it only if the replacement still runs after <s> seconds
|
|
58
|
+
--snapshot-wait <s> Wait up to <s> seconds for another snapshot on this host (exit 75 after)
|
|
59
|
+
--snapshot-disk-wait <s> Wait up to <s> seconds for free disk instead of refusing (exit 75 after)
|
|
60
|
+
--snapshot-size-hint <bytes> Use this writable-layer size instead of docker inspect --size
|
|
61
|
+
--snapshot-prepare <cmd> docker exec <cmd> in the started original before it is snapshotted
|
|
57
62
|
--resume-all Re-attach or reconcile every execution still marked running
|
|
58
63
|
--cleanup Clean up stale "executing" records (crashed/killed processes)
|
|
59
64
|
--cleanup-dry-run Show stale records that would be cleaned up (without cleaning)
|
|
65
|
+
--cleanup <id> Remove a Docker execution's containers and snapshot images (never forced)
|
|
66
|
+
--cleanup-dry-run <id> Show the containers and images --cleanup <id> would remove
|
|
60
67
|
--version, -v Show version information
|
|
61
68
|
|
|
62
69
|
Examples:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { describe, it, expect } = require('bun:test');
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const CLI = path.join(__dirname, '../src/bin/cli.js');
|
|
8
|
+
|
|
9
|
+
// Run the CLI with the read ends of the given output pipes closed before it
|
|
10
|
+
// starts writing, like `$ --version | head -1` once `head` has exited.
|
|
11
|
+
function runWithClosedPipes(args, closed) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const child = spawn(process.execPath, [CLI, ...args], {
|
|
14
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
START_APP_FOLDER: fs.mkdtempSync(path.join(os.tmpdir(), 'start-222-')),
|
|
18
|
+
START_DISABLE_AUTO_ISSUE: '1',
|
|
19
|
+
START_DISABLE_LOG_UPLOAD: '1',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
let stderr = '';
|
|
23
|
+
for (const name of ['stdout', 'stderr']) {
|
|
24
|
+
if (closed.includes(name)) {
|
|
25
|
+
child[name].destroy();
|
|
26
|
+
} else {
|
|
27
|
+
child[name].on('data', (chunk) => (stderr += chunk));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
child.on('close', (code) => resolve({ code, stderr }));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('closed output pipes (#222)', () => {
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const big = 'seq 1 200000';
|
|
39
|
+
for (const args of [['--version'], ['-v'], ['--help']]) {
|
|
40
|
+
it(`${args[0]} exits 0 when stdout is closed`, async () => {
|
|
41
|
+
const result = await runWithClosedPipes(args, ['stdout']);
|
|
42
|
+
expect(result.stderr).not.toContain('EPIPE');
|
|
43
|
+
expect(result.code).toBe(0);
|
|
44
|
+
}, 15000);
|
|
45
|
+
}
|
|
46
|
+
it('a command keeps its exit code when stdout is closed', async () => {
|
|
47
|
+
const result = await runWithClosedPipes(
|
|
48
|
+
['--', 'sh', '-c', `${big}; exit 3`],
|
|
49
|
+
['stdout']
|
|
50
|
+
);
|
|
51
|
+
expect(result.stderr).not.toContain('EPIPE');
|
|
52
|
+
expect(result.code).toBe(3);
|
|
53
|
+
}, 15000);
|
|
54
|
+
it('a command keeps its exit code when stdout and stderr are closed', async () => {
|
|
55
|
+
const result = await runWithClosedPipes(
|
|
56
|
+
['--', 'sh', '-c', `${big}; ${big} >&2; exit 3`],
|
|
57
|
+
['stdout', 'stderr']
|
|
58
|
+
);
|
|
59
|
+
expect(result.code).toBe(3);
|
|
60
|
+
}, 15000);
|
|
61
|
+
});
|