start-command 0.13.0 → 0.15.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 +28 -231
- package/bun.lock +5 -0
- package/eslint.config.mjs +1 -1
- package/package.json +11 -6
- package/src/bin/cli.js +275 -137
- package/src/lib/args-parser.js +118 -0
- package/src/lib/execution-store.js +722 -0
- package/src/lib/isolation.js +51 -0
- package/src/lib/status-formatter.js +121 -0
- package/src/lib/version.js +143 -0
- package/test/args-parser.test.js +107 -0
- package/test/cli.test.js +11 -1
- package/test/docker-autoremove.test.js +11 -16
- package/test/execution-store.test.js +483 -0
- package/test/isolation-cleanup.test.js +11 -16
- package/test/isolation.test.js +11 -17
- package/test/public-exports.test.js +105 -0
- package/test/status-query.test.js +195 -0
- package/.github/workflows/release.yml +0 -352
- package/.husky/pre-commit +0 -1
- package/ARCHITECTURE.md +0 -297
- package/LICENSE +0 -24
- package/README.md +0 -339
- package/REQUIREMENTS.md +0 -299
- package/docs/PIPES.md +0 -243
- package/docs/USAGE.md +0 -194
- package/docs/case-studies/issue-15/README.md +0 -208
- package/docs/case-studies/issue-18/README.md +0 -343
- package/docs/case-studies/issue-18/issue-comments.json +0 -1
- package/docs/case-studies/issue-18/issue-data.json +0 -7
- package/docs/case-studies/issue-22/analysis.md +0 -547
- package/docs/case-studies/issue-22/issue-data.json +0 -12
- package/docs/case-studies/issue-25/README.md +0 -232
- package/docs/case-studies/issue-25/issue-data.json +0 -21
- package/docs/case-studies/issue-28/README.md +0 -405
- package/docs/case-studies/issue-28/issue-data.json +0 -105
- package/docs/case-studies/issue-28/raw-issue-data.md +0 -92
- package/experiments/debug-regex.js +0 -49
- package/experiments/isolation-design.md +0 -131
- package/experiments/screen-output-test.js +0 -265
- package/experiments/test-cli.sh +0 -42
- package/experiments/test-command-stream-cjs.cjs +0 -30
- package/experiments/test-command-stream-wrapper.js +0 -54
- package/experiments/test-command-stream.mjs +0 -56
- package/experiments/test-screen-attached.js +0 -126
- package/experiments/test-screen-logfile.js +0 -286
- package/experiments/test-screen-modes.js +0 -128
- package/experiments/test-screen-output.sh +0 -27
- package/experiments/test-screen-tee-debug.js +0 -237
- package/experiments/test-screen-tee-fallback.js +0 -230
- package/experiments/test-substitution.js +0 -143
- package/experiments/user-isolation-research.md +0 -83
- package/scripts/changeset-version.mjs +0 -38
- package/scripts/check-file-size.mjs +0 -103
- package/scripts/create-github-release.mjs +0 -93
- package/scripts/create-manual-changeset.mjs +0 -89
- package/scripts/format-github-release.mjs +0 -83
- package/scripts/format-release-notes.mjs +0 -219
- package/scripts/instant-version-bump.mjs +0 -121
- package/scripts/publish-to-npm.mjs +0 -129
- package/scripts/setup-npm.mjs +0 -37
- package/scripts/validate-changeset.mjs +0 -107
- package/scripts/version-and-commit.mjs +0 -237
package/src/lib/isolation.js
CHANGED
|
@@ -914,6 +914,56 @@ function runAsIsolatedUser(cmd, username) {
|
|
|
914
914
|
});
|
|
915
915
|
}
|
|
916
916
|
|
|
917
|
+
/**
|
|
918
|
+
* Check if the Docker daemon can run Linux container images
|
|
919
|
+
* On Windows with Docker Desktop in Windows containers mode,
|
|
920
|
+
* Linux images like alpine:latest cannot be pulled or run.
|
|
921
|
+
* @returns {boolean} True if Linux Docker images can be run
|
|
922
|
+
*/
|
|
923
|
+
function canRunLinuxDockerImages() {
|
|
924
|
+
if (!isCommandAvailable('docker')) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
try {
|
|
929
|
+
// First check if Docker daemon is running
|
|
930
|
+
execSync('docker info', { stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 });
|
|
931
|
+
|
|
932
|
+
// On Windows, check if Docker is configured for Linux containers
|
|
933
|
+
if (process.platform === 'win32') {
|
|
934
|
+
try {
|
|
935
|
+
const osType = execSync('docker info --format "{{.OSType}}"', {
|
|
936
|
+
encoding: 'utf8',
|
|
937
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
938
|
+
timeout: 5000,
|
|
939
|
+
}).trim();
|
|
940
|
+
|
|
941
|
+
// Docker must be using Linux containers to run Linux images
|
|
942
|
+
if (osType !== 'linux') {
|
|
943
|
+
if (DEBUG) {
|
|
944
|
+
console.log(
|
|
945
|
+
`[DEBUG] Docker is running in ${osType} containers mode, cannot run Linux images`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
} catch {
|
|
951
|
+
// If we can't determine the OS type, assume Linux images won't work on Windows
|
|
952
|
+
if (DEBUG) {
|
|
953
|
+
console.log(
|
|
954
|
+
'[DEBUG] Could not determine Docker OS type, assuming Linux images unavailable'
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return true;
|
|
962
|
+
} catch {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
917
967
|
module.exports = {
|
|
918
968
|
isCommandAvailable,
|
|
919
969
|
hasTTY,
|
|
@@ -934,4 +984,5 @@ module.exports = {
|
|
|
934
984
|
getScreenVersion,
|
|
935
985
|
supportsLogfileOption,
|
|
936
986
|
resetScreenVersionCache,
|
|
987
|
+
canRunLinuxDockerImages,
|
|
937
988
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status formatter module for execution records
|
|
3
|
+
*
|
|
4
|
+
* Provides formatting functions for execution status output in various formats:
|
|
5
|
+
* - Links Notation (links-notation): Structured link doublet format
|
|
6
|
+
* - JSON: Standard JSON output
|
|
7
|
+
* - Text: Human-readable text format
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format execution record as Links Notation (indented style)
|
|
12
|
+
* @param {Object} record - The execution record with toObject() method
|
|
13
|
+
* @returns {string} Links Notation formatted string in indented style
|
|
14
|
+
*
|
|
15
|
+
* Output format:
|
|
16
|
+
* <uuid>
|
|
17
|
+
* <key> "<value>"
|
|
18
|
+
* ...
|
|
19
|
+
*/
|
|
20
|
+
function formatRecordAsLinksNotation(record) {
|
|
21
|
+
const obj = record.toObject();
|
|
22
|
+
const lines = [record.uuid];
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
25
|
+
if (value !== null && value !== undefined) {
|
|
26
|
+
// Format value based on type
|
|
27
|
+
let formattedValue;
|
|
28
|
+
if (typeof value === 'object') {
|
|
29
|
+
formattedValue = JSON.stringify(value);
|
|
30
|
+
} else {
|
|
31
|
+
formattedValue = String(value);
|
|
32
|
+
}
|
|
33
|
+
// Escape quotes in the value
|
|
34
|
+
const escapedValue = formattedValue.replace(/"/g, '\\"');
|
|
35
|
+
lines.push(` ${key} "${escapedValue}"`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format execution record as human-readable text
|
|
44
|
+
* @param {Object} record - The execution record with toObject() method
|
|
45
|
+
* @returns {string} Human-readable text
|
|
46
|
+
*/
|
|
47
|
+
function formatRecordAsText(record) {
|
|
48
|
+
const obj = record.toObject();
|
|
49
|
+
const lines = [
|
|
50
|
+
`Execution Status`,
|
|
51
|
+
`${'='.repeat(50)}`,
|
|
52
|
+
`UUID: ${obj.uuid}`,
|
|
53
|
+
`Status: ${obj.status}`,
|
|
54
|
+
`Command: ${obj.command}`,
|
|
55
|
+
`Exit Code: ${obj.exitCode !== null ? obj.exitCode : 'N/A'}`,
|
|
56
|
+
`PID: ${obj.pid !== null ? obj.pid : 'N/A'}`,
|
|
57
|
+
`Working Directory: ${obj.workingDirectory}`,
|
|
58
|
+
`Shell: ${obj.shell}`,
|
|
59
|
+
`Platform: ${obj.platform}`,
|
|
60
|
+
`Start Time: ${obj.startTime}`,
|
|
61
|
+
`End Time: ${obj.endTime || 'N/A'}`,
|
|
62
|
+
`Log Path: ${obj.logPath}`,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (Object.keys(obj.options).length > 0) {
|
|
66
|
+
lines.push(`Options: ${JSON.stringify(obj.options)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format execution record based on format type
|
|
74
|
+
* @param {Object} record - The execution record
|
|
75
|
+
* @param {string} format - Output format (links-notation, json, text)
|
|
76
|
+
* @returns {string} Formatted output string
|
|
77
|
+
*/
|
|
78
|
+
function formatRecord(record, format) {
|
|
79
|
+
switch (format) {
|
|
80
|
+
case 'links-notation':
|
|
81
|
+
return formatRecordAsLinksNotation(record);
|
|
82
|
+
case 'json':
|
|
83
|
+
return JSON.stringify(record.toObject(), null, 2);
|
|
84
|
+
case 'text':
|
|
85
|
+
return formatRecordAsText(record);
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unknown output format: ${format}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handle status query and output the result
|
|
93
|
+
* @param {Object} store - ExecutionStore instance
|
|
94
|
+
* @param {string} uuid - UUID of the execution to query
|
|
95
|
+
* @param {string|null} outputFormat - Output format (links-notation, json, text)
|
|
96
|
+
* @returns {{success: boolean, output?: string, error?: string}}
|
|
97
|
+
*/
|
|
98
|
+
function queryStatus(store, uuid, outputFormat) {
|
|
99
|
+
if (!store) {
|
|
100
|
+
return { success: false, error: 'Execution tracking is disabled.' };
|
|
101
|
+
}
|
|
102
|
+
const record = store.get(uuid);
|
|
103
|
+
if (!record) {
|
|
104
|
+
return { success: false, error: `No execution found with UUID: ${uuid}` };
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
output: formatRecord(record, outputFormat || 'links-notation'),
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return { success: false, error: err.message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
formatRecordAsLinksNotation,
|
|
118
|
+
formatRecordAsText,
|
|
119
|
+
formatRecord,
|
|
120
|
+
queryStatus,
|
|
121
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version and tool information utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { execSync, spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Print version information
|
|
10
|
+
* @param {boolean} verbose - Whether to show verbose debugging info
|
|
11
|
+
*/
|
|
12
|
+
function printVersion(verbose = false) {
|
|
13
|
+
// Get package version
|
|
14
|
+
const packageJson = require('../../package.json');
|
|
15
|
+
const startCommandVersion = packageJson.version;
|
|
16
|
+
|
|
17
|
+
console.log(`start-command version: ${startCommandVersion}`);
|
|
18
|
+
console.log('');
|
|
19
|
+
|
|
20
|
+
// Get runtime information (Bun or Node.js)
|
|
21
|
+
const runtime = typeof Bun !== 'undefined' ? 'Bun' : 'Node.js';
|
|
22
|
+
const runtimeVersion =
|
|
23
|
+
typeof Bun !== 'undefined' ? Bun.version : process.version;
|
|
24
|
+
|
|
25
|
+
// Get OS information
|
|
26
|
+
console.log(`OS: ${process.platform}`);
|
|
27
|
+
|
|
28
|
+
// Get OS version (use sw_vers on macOS for user-friendly version)
|
|
29
|
+
let osVersion = os.release();
|
|
30
|
+
if (process.platform === 'darwin') {
|
|
31
|
+
try {
|
|
32
|
+
osVersion = execSync('sw_vers -productVersion', {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
timeout: 5000,
|
|
35
|
+
}).trim();
|
|
36
|
+
if (verbose) {
|
|
37
|
+
console.log(`[verbose] macOS version from sw_vers: ${osVersion}`);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Fallback to kernel version if sw_vers fails
|
|
41
|
+
osVersion = os.release();
|
|
42
|
+
if (verbose) {
|
|
43
|
+
console.log(
|
|
44
|
+
`[verbose] sw_vers failed, using kernel version: ${osVersion}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`OS Version: ${osVersion}`);
|
|
51
|
+
console.log(`${runtime} Version: ${runtimeVersion}`);
|
|
52
|
+
console.log(`Architecture: ${process.arch}`);
|
|
53
|
+
console.log('');
|
|
54
|
+
|
|
55
|
+
// Check for installed isolation tools
|
|
56
|
+
console.log('Isolation tools:');
|
|
57
|
+
|
|
58
|
+
if (verbose) {
|
|
59
|
+
console.log('[verbose] Checking isolation tools...');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check screen (use -v flag for compatibility with older versions)
|
|
63
|
+
const screenVersion = getToolVersion('screen', '-v', verbose);
|
|
64
|
+
if (screenVersion) {
|
|
65
|
+
console.log(` screen: ${screenVersion}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(' screen: not installed');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check tmux
|
|
71
|
+
const tmuxVersion = getToolVersion('tmux', '-V', verbose);
|
|
72
|
+
if (tmuxVersion) {
|
|
73
|
+
console.log(` tmux: ${tmuxVersion}`);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(' tmux: not installed');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check docker
|
|
79
|
+
const dockerVersion = getToolVersion('docker', '--version', verbose);
|
|
80
|
+
if (dockerVersion) {
|
|
81
|
+
console.log(` docker: ${dockerVersion}`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log(' docker: not installed');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get version of an installed tool
|
|
89
|
+
* @param {string} toolName - Name of the tool
|
|
90
|
+
* @param {string} versionFlag - Flag to get version (e.g., '--version', '-V')
|
|
91
|
+
* @param {boolean} verbose - Whether to log verbose information
|
|
92
|
+
* @returns {string|null} Version string or null if not installed
|
|
93
|
+
*/
|
|
94
|
+
function getToolVersion(toolName, versionFlag, verbose = false) {
|
|
95
|
+
const isWindows = process.platform === 'win32';
|
|
96
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
97
|
+
|
|
98
|
+
// First, check if the tool exists in PATH
|
|
99
|
+
try {
|
|
100
|
+
execSync(`${whichCmd} ${toolName}`, {
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
timeout: 5000,
|
|
103
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
// Tool not found in PATH
|
|
107
|
+
if (verbose) {
|
|
108
|
+
console.log(`[verbose] ${toolName}: not found in PATH`);
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Tool exists, try to get version using spawnSync
|
|
114
|
+
// This captures output regardless of exit code (some tools like older screen
|
|
115
|
+
// versions return non-zero exit code even when showing version successfully)
|
|
116
|
+
const result = spawnSync(toolName, [versionFlag], {
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
timeout: 5000,
|
|
119
|
+
shell: false,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Combine stdout and stderr (some tools output version to stderr)
|
|
123
|
+
const output = ((result.stdout || '') + (result.stderr || '')).trim();
|
|
124
|
+
|
|
125
|
+
if (verbose) {
|
|
126
|
+
console.log(
|
|
127
|
+
`[verbose] ${toolName} ${versionFlag}: exit=${result.status}, output="${output.substring(0, 100)}"`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!output) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Return the first line of output
|
|
136
|
+
const firstLine = output.split('\n')[0];
|
|
137
|
+
return firstLine || null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
printVersion,
|
|
142
|
+
getToolVersion,
|
|
143
|
+
};
|
package/test/args-parser.test.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
hasIsolation,
|
|
14
14
|
getEffectiveMode,
|
|
15
15
|
VALID_BACKENDS,
|
|
16
|
+
VALID_OUTPUT_FORMATS,
|
|
16
17
|
} = require('../src/lib/args-parser');
|
|
17
18
|
|
|
18
19
|
describe('parseArgs', () => {
|
|
@@ -782,3 +783,109 @@ describe('keep-user option', () => {
|
|
|
782
783
|
assert.strictEqual(result.wrapperOptions.keepUser, true);
|
|
783
784
|
});
|
|
784
785
|
});
|
|
786
|
+
|
|
787
|
+
describe('status option', () => {
|
|
788
|
+
it('should parse --status with UUID', () => {
|
|
789
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
790
|
+
const result = parseArgs(['--status', uuid]);
|
|
791
|
+
assert.strictEqual(result.wrapperOptions.status, uuid);
|
|
792
|
+
assert.strictEqual(result.command, '');
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should parse --status=value format', () => {
|
|
796
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
797
|
+
const result = parseArgs([`--status=${uuid}`]);
|
|
798
|
+
assert.strictEqual(result.wrapperOptions.status, uuid);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('should throw error for missing UUID argument', () => {
|
|
802
|
+
assert.throws(() => {
|
|
803
|
+
parseArgs(['--status']);
|
|
804
|
+
}, /requires a UUID argument/);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should throw error for --status with -flag as next argument', () => {
|
|
808
|
+
assert.throws(() => {
|
|
809
|
+
parseArgs(['--status', '--output-format']);
|
|
810
|
+
}, /requires a UUID argument/);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it('should default status to null', () => {
|
|
814
|
+
const result = parseArgs(['echo', 'hello']);
|
|
815
|
+
assert.strictEqual(result.wrapperOptions.status, null);
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
describe('output-format option', () => {
|
|
820
|
+
it('should parse --output-format with --status', () => {
|
|
821
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
822
|
+
const result = parseArgs([
|
|
823
|
+
'--status',
|
|
824
|
+
uuid,
|
|
825
|
+
'--output-format',
|
|
826
|
+
'links-notation',
|
|
827
|
+
]);
|
|
828
|
+
assert.strictEqual(result.wrapperOptions.status, uuid);
|
|
829
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, 'links-notation');
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
it('should parse --output-format=value format', () => {
|
|
833
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
834
|
+
const result = parseArgs(['--status', uuid, '--output-format=json']);
|
|
835
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, 'json');
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('should normalize format to lowercase', () => {
|
|
839
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
840
|
+
const result = parseArgs(['--status', uuid, '--output-format', 'JSON']);
|
|
841
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, 'json');
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('should throw error for missing format argument', () => {
|
|
845
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
846
|
+
assert.throws(() => {
|
|
847
|
+
parseArgs(['--status', uuid, '--output-format']);
|
|
848
|
+
}, /requires a format argument/);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('should throw error for invalid format', () => {
|
|
852
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
853
|
+
assert.throws(() => {
|
|
854
|
+
parseArgs(['--status', uuid, '--output-format', 'invalid']);
|
|
855
|
+
}, /Invalid output format/);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it('should throw error for output-format without status', () => {
|
|
859
|
+
assert.throws(() => {
|
|
860
|
+
parseArgs(['--output-format', 'json', '--', 'npm', 'test']);
|
|
861
|
+
}, /--output-format option is only valid with --status/);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('should accept all valid output formats', () => {
|
|
865
|
+
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
866
|
+
for (const format of VALID_OUTPUT_FORMATS) {
|
|
867
|
+
assert.doesNotThrow(() => {
|
|
868
|
+
parseArgs(['--status', uuid, '--output-format', format]);
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it('should default outputFormat to null', () => {
|
|
874
|
+
const result = parseArgs(['echo', 'hello']);
|
|
875
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, null);
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
describe('VALID_OUTPUT_FORMATS', () => {
|
|
880
|
+
it('should include links-notation', () => {
|
|
881
|
+
assert.ok(VALID_OUTPUT_FORMATS.includes('links-notation'));
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('should include json', () => {
|
|
885
|
+
assert.ok(VALID_OUTPUT_FORMATS.includes('json'));
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it('should include text', () => {
|
|
889
|
+
assert.ok(VALID_OUTPUT_FORMATS.includes('text'));
|
|
890
|
+
});
|
|
891
|
+
});
|
package/test/cli.test.js
CHANGED
|
@@ -13,10 +13,14 @@ const fs = require('fs');
|
|
|
13
13
|
// Path to the CLI script
|
|
14
14
|
const CLI_PATH = path.join(__dirname, '../src/bin/cli.js');
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Timeout for CLI operations - longer on Windows due to cold-start latency
|
|
17
|
+
const CLI_TIMEOUT = process.platform === 'win32' ? 30000 : 10000;
|
|
18
|
+
|
|
19
|
+
// Helper to run CLI with timeout
|
|
17
20
|
function runCLI(args = []) {
|
|
18
21
|
return spawnSync('bun', [CLI_PATH, ...args], {
|
|
19
22
|
encoding: 'utf8',
|
|
23
|
+
timeout: CLI_TIMEOUT,
|
|
20
24
|
env: {
|
|
21
25
|
...process.env,
|
|
22
26
|
START_DISABLE_AUTO_ISSUE: '1',
|
|
@@ -29,6 +33,12 @@ describe('CLI version flag', () => {
|
|
|
29
33
|
it('should display version with --version', () => {
|
|
30
34
|
const result = runCLI(['--version']);
|
|
31
35
|
|
|
36
|
+
// Check if process was killed (e.g., due to timeout)
|
|
37
|
+
assert.notStrictEqual(
|
|
38
|
+
result.status,
|
|
39
|
+
null,
|
|
40
|
+
`Process should complete (was killed with signal: ${result.signal})`
|
|
41
|
+
);
|
|
32
42
|
assert.strictEqual(result.status, 0, 'Exit code should be 0');
|
|
33
43
|
|
|
34
44
|
// Check for key elements in version output
|
|
@@ -21,18 +21,9 @@ async function waitFor(conditionFn, timeout = 5000, interval = 100) {
|
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
execSync('docker info', { stdio: 'ignore', timeout: 5000 });
|
|
31
|
-
return true;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
24
|
+
// Use the canRunLinuxDockerImages function from isolation module
|
|
25
|
+
// to properly detect if Linux containers can run (handles Windows containers mode)
|
|
26
|
+
const { canRunLinuxDockerImages } = require('../src/lib/isolation');
|
|
36
27
|
|
|
37
28
|
describe('Docker Auto-Remove Container Feature', () => {
|
|
38
29
|
// These tests verify the --auto-remove-docker-container option
|
|
@@ -40,8 +31,10 @@ describe('Docker Auto-Remove Container Feature', () => {
|
|
|
40
31
|
|
|
41
32
|
describe('auto-remove enabled', () => {
|
|
42
33
|
it('should automatically remove container when autoRemoveDockerContainer is true', async () => {
|
|
43
|
-
if (!
|
|
44
|
-
console.log(
|
|
34
|
+
if (!canRunLinuxDockerImages()) {
|
|
35
|
+
console.log(
|
|
36
|
+
' Skipping: docker not available, daemon not running, or Linux containers not supported'
|
|
37
|
+
);
|
|
45
38
|
return;
|
|
46
39
|
}
|
|
47
40
|
|
|
@@ -103,8 +96,10 @@ describe('Docker Auto-Remove Container Feature', () => {
|
|
|
103
96
|
|
|
104
97
|
describe('auto-remove disabled (default)', () => {
|
|
105
98
|
it('should preserve container filesystem by default (without autoRemoveDockerContainer)', async () => {
|
|
106
|
-
if (!
|
|
107
|
-
console.log(
|
|
99
|
+
if (!canRunLinuxDockerImages()) {
|
|
100
|
+
console.log(
|
|
101
|
+
' Skipping: docker not available, daemon not running, or Linux containers not supported'
|
|
102
|
+
);
|
|
108
103
|
return;
|
|
109
104
|
}
|
|
110
105
|
|