start-command 0.11.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 -217
- package/bun.lock +10 -0
- package/eslint.config.mjs +1 -1
- package/package.json +13 -5
- package/src/bin/cli.js +414 -499
- package/src/lib/args-parser.js +126 -0
- package/src/lib/command-stream.js +258 -0
- package/src/lib/execution-store.js +722 -0
- package/src/lib/failure-handler.js +397 -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 +140 -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 -334
- 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-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/args-parser.js
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* --keep-user Keep isolated user after command completes (don't delete)
|
|
17
17
|
* --keep-alive, -k Keep isolation environment alive after command exits
|
|
18
18
|
* --auto-remove-docker-container Automatically remove docker container after exit (disabled by default)
|
|
19
|
+
* --use-command-stream Use command-stream library for command execution (experimental)
|
|
20
|
+
* --status <uuid> Show status of a previous command execution by UUID
|
|
21
|
+
* --output-format <format> Output format for status (links-notation, json, text)
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
// Debug mode from environment
|
|
@@ -27,6 +30,45 @@ const DEBUG =
|
|
|
27
30
|
*/
|
|
28
31
|
const VALID_BACKENDS = ['screen', 'tmux', 'docker', 'ssh'];
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Valid output formats for --status
|
|
35
|
+
*/
|
|
36
|
+
const VALID_OUTPUT_FORMATS = ['links-notation', 'json', 'text'];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* UUID v4 regex pattern for validation
|
|
40
|
+
*/
|
|
41
|
+
const UUID_REGEX =
|
|
42
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a string is a valid UUID v4
|
|
46
|
+
* @param {string} str - String to validate
|
|
47
|
+
* @returns {boolean} True if valid UUID v4
|
|
48
|
+
*/
|
|
49
|
+
function isValidUUID(str) {
|
|
50
|
+
return UUID_REGEX.test(str);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate a UUID v4
|
|
55
|
+
* @returns {string} A new UUID v4 string
|
|
56
|
+
*/
|
|
57
|
+
function generateUUID() {
|
|
58
|
+
// Try to use Node.js/Bun crypto module
|
|
59
|
+
try {
|
|
60
|
+
const crypto = require('crypto');
|
|
61
|
+
return crypto.randomUUID();
|
|
62
|
+
} catch {
|
|
63
|
+
// Fallback for environments without crypto.randomUUID
|
|
64
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
65
|
+
const r = (Math.random() * 16) | 0;
|
|
66
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
67
|
+
return v.toString(16);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
30
72
|
/**
|
|
31
73
|
* Parse command line arguments into wrapper options and command
|
|
32
74
|
* @param {string[]} args - Array of command line arguments
|
|
@@ -38,6 +80,7 @@ function parseArgs(args) {
|
|
|
38
80
|
attached: false, // Run in attached mode
|
|
39
81
|
detached: false, // Run in detached mode
|
|
40
82
|
session: null, // Session name
|
|
83
|
+
sessionId: null, // Session ID (UUID) for tracking - auto-generated if not provided
|
|
41
84
|
image: null, // Docker image
|
|
42
85
|
endpoint: null, // SSH endpoint (e.g., user@host)
|
|
43
86
|
user: false, // Create isolated user
|
|
@@ -45,6 +88,9 @@ function parseArgs(args) {
|
|
|
45
88
|
keepUser: false, // Keep isolated user after command completes (don't delete)
|
|
46
89
|
keepAlive: false, // Keep environment alive after command exits
|
|
47
90
|
autoRemoveDockerContainer: false, // Auto-remove docker container after exit
|
|
91
|
+
useCommandStream: false, // Use command-stream library for command execution
|
|
92
|
+
status: null, // UUID to show status for
|
|
93
|
+
outputFormat: null, // Output format for status (links-notation, json, text)
|
|
48
94
|
};
|
|
49
95
|
|
|
50
96
|
let commandArgs = [];
|
|
@@ -239,6 +285,60 @@ function parseOption(args, index, options) {
|
|
|
239
285
|
return 1;
|
|
240
286
|
}
|
|
241
287
|
|
|
288
|
+
// --use-command-stream
|
|
289
|
+
if (arg === '--use-command-stream') {
|
|
290
|
+
options.useCommandStream = true;
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// --session-id or --session-name (alias) <uuid>
|
|
295
|
+
if (arg === '--session-id' || arg === '--session-name') {
|
|
296
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
297
|
+
options.sessionId = args[index + 1];
|
|
298
|
+
return 2;
|
|
299
|
+
} else {
|
|
300
|
+
throw new Error(`Option ${arg} requires a UUID argument`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// --session-id=<value> or --session-name=<value>
|
|
305
|
+
if (arg.startsWith('--session-id=') || arg.startsWith('--session-name=')) {
|
|
306
|
+
options.sessionId = arg.split('=')[1];
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --status <uuid>
|
|
311
|
+
if (arg === '--status') {
|
|
312
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
313
|
+
options.status = args[index + 1];
|
|
314
|
+
return 2;
|
|
315
|
+
} else {
|
|
316
|
+
throw new Error(`Option ${arg} requires a UUID argument`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// --status=<value>
|
|
321
|
+
if (arg.startsWith('--status=')) {
|
|
322
|
+
options.status = arg.split('=')[1];
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// --output-format <format>
|
|
327
|
+
if (arg === '--output-format') {
|
|
328
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
329
|
+
options.outputFormat = args[index + 1].toLowerCase();
|
|
330
|
+
return 2;
|
|
331
|
+
} else {
|
|
332
|
+
throw new Error(`Option ${arg} requires a format argument`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// --output-format=<value>
|
|
337
|
+
if (arg.startsWith('--output-format=')) {
|
|
338
|
+
options.outputFormat = arg.split('=')[1].toLowerCase();
|
|
339
|
+
return 1;
|
|
340
|
+
}
|
|
341
|
+
|
|
242
342
|
// Not a recognized wrapper option
|
|
243
343
|
return 0;
|
|
244
344
|
}
|
|
@@ -333,6 +433,29 @@ function validateOptions(options) {
|
|
|
333
433
|
if (options.keepUser && !options.user) {
|
|
334
434
|
throw new Error('--keep-user option is only valid with --isolated-user');
|
|
335
435
|
}
|
|
436
|
+
|
|
437
|
+
// Validate output format
|
|
438
|
+
if (options.outputFormat !== null && options.outputFormat !== undefined) {
|
|
439
|
+
if (!VALID_OUTPUT_FORMATS.includes(options.outputFormat)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`Invalid output format: "${options.outputFormat}". Valid options are: ${VALID_OUTPUT_FORMATS.join(', ')}`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Output format is only valid with --status
|
|
447
|
+
if (options.outputFormat && !options.status) {
|
|
448
|
+
throw new Error('--output-format option is only valid with --status');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Validate session ID is a valid UUID if provided
|
|
452
|
+
if (options.sessionId !== null && options.sessionId !== undefined) {
|
|
453
|
+
if (!isValidUUID(options.sessionId)) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Invalid session ID: "${options.sessionId}". Session ID must be a valid UUID v4.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
336
459
|
}
|
|
337
460
|
|
|
338
461
|
/**
|
|
@@ -375,5 +498,8 @@ module.exports = {
|
|
|
375
498
|
generateSessionName,
|
|
376
499
|
hasIsolation,
|
|
377
500
|
getEffectiveMode,
|
|
501
|
+
isValidUUID,
|
|
502
|
+
generateUUID,
|
|
378
503
|
VALID_BACKENDS,
|
|
504
|
+
VALID_OUTPUT_FORMATS,
|
|
379
505
|
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command-Stream Wrapper for start-command
|
|
3
|
+
*
|
|
4
|
+
* This module provides a bridge to the command-stream library, which uses ESM,
|
|
5
|
+
* from the CommonJS-based start-command codebase.
|
|
6
|
+
*
|
|
7
|
+
* The command-stream library provides:
|
|
8
|
+
* - Shell command execution with streaming support
|
|
9
|
+
* - Synchronous and asynchronous execution modes
|
|
10
|
+
* - Built-in virtual commands (echo, ls, pwd, cd, etc.)
|
|
11
|
+
* - Real-time output capture
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Debug mode from environment
|
|
15
|
+
const DEBUG =
|
|
16
|
+
process.env.START_DEBUG === '1' || process.env.START_DEBUG === 'true';
|
|
17
|
+
|
|
18
|
+
// Cached command-stream module
|
|
19
|
+
let commandStream = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the command-stream module (lazy-loaded)
|
|
23
|
+
* @returns {Promise<object>} The command-stream module
|
|
24
|
+
*/
|
|
25
|
+
async function getCommandStream() {
|
|
26
|
+
if (!commandStream) {
|
|
27
|
+
commandStream = await import('command-stream');
|
|
28
|
+
}
|
|
29
|
+
return commandStream;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execute a shell command synchronously and return the result
|
|
34
|
+
* Uses command-stream's $ function with .sync() for blocking execution.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} command - The shell command to execute
|
|
37
|
+
* @param {object} options - Options for command execution
|
|
38
|
+
* @param {boolean} options.silent - If true, don't mirror output to console (default: true)
|
|
39
|
+
* @param {boolean} options.captureOutput - If true, capture stdout/stderr (default: true)
|
|
40
|
+
* @returns {Promise<{stdout: string, stderr: string, code: number}>} Command result
|
|
41
|
+
*/
|
|
42
|
+
async function execCommand(command, options = {}) {
|
|
43
|
+
const { $ } = await getCommandStream();
|
|
44
|
+
|
|
45
|
+
const silent = options.silent !== false;
|
|
46
|
+
|
|
47
|
+
// Create a configured $ instance
|
|
48
|
+
const $cmd = $({ mirror: !silent, capture: true });
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Use sync() for synchronous execution
|
|
52
|
+
const result = $cmd`${command}`.sync();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
stdout: (result.stdout || '').trim(),
|
|
56
|
+
stderr: (result.stderr || '').trim(),
|
|
57
|
+
code: result.code || 0,
|
|
58
|
+
};
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (DEBUG) {
|
|
61
|
+
console.log(`[DEBUG] execCommand error: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
stdout: '',
|
|
65
|
+
stderr: err.message || '',
|
|
66
|
+
code: 1,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute a shell command asynchronously and return the result
|
|
73
|
+
*
|
|
74
|
+
* @param {string} command - The shell command to execute
|
|
75
|
+
* @param {object} options - Options for command execution
|
|
76
|
+
* @param {boolean} options.silent - If true, don't mirror output to console (default: true)
|
|
77
|
+
* @param {boolean} options.captureOutput - If true, capture stdout/stderr (default: true)
|
|
78
|
+
* @returns {Promise<{stdout: string, stderr: string, code: number}>} Command result
|
|
79
|
+
*/
|
|
80
|
+
async function execCommandAsync(command, options = {}) {
|
|
81
|
+
const { $ } = await getCommandStream();
|
|
82
|
+
|
|
83
|
+
const silent = options.silent !== false;
|
|
84
|
+
|
|
85
|
+
// Create a configured $ instance
|
|
86
|
+
const $cmd = $({ mirror: !silent, capture: true });
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = await $cmd`${command}`;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
stdout: (result.stdout || '').trim(),
|
|
93
|
+
stderr: (result.stderr || '').trim(),
|
|
94
|
+
code: result.code || 0,
|
|
95
|
+
};
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (DEBUG) {
|
|
98
|
+
console.log(`[DEBUG] execCommandAsync error: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
stdout: '',
|
|
102
|
+
stderr: err.message || '',
|
|
103
|
+
code: 1,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if a command exists in the PATH
|
|
110
|
+
*
|
|
111
|
+
* @param {string} commandName - The command to check for
|
|
112
|
+
* @returns {Promise<boolean>} True if the command exists
|
|
113
|
+
*/
|
|
114
|
+
async function commandExists(commandName) {
|
|
115
|
+
const isWindows = process.platform === 'win32';
|
|
116
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
117
|
+
|
|
118
|
+
const result = await execCommand(`${whichCmd} ${commandName}`);
|
|
119
|
+
return result.code === 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get the path to a command
|
|
124
|
+
*
|
|
125
|
+
* @param {string} commandName - The command to find
|
|
126
|
+
* @returns {Promise<string|null>} Path to the command or null if not found
|
|
127
|
+
*/
|
|
128
|
+
async function getCommandPath(commandName) {
|
|
129
|
+
const isWindows = process.platform === 'win32';
|
|
130
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
131
|
+
|
|
132
|
+
const result = await execCommand(`${whichCmd} ${commandName}`);
|
|
133
|
+
if (result.code === 0 && result.stdout) {
|
|
134
|
+
// On Windows, where returns multiple lines, take the first
|
|
135
|
+
return result.stdout.split('\n')[0].trim();
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the version of a tool by running it with a version flag
|
|
142
|
+
*
|
|
143
|
+
* @param {string} toolName - Name of the tool
|
|
144
|
+
* @param {string} versionFlag - Flag to get version (e.g., '--version', '-V')
|
|
145
|
+
* @param {boolean} verbose - Whether to log verbose information
|
|
146
|
+
* @returns {Promise<string|null>} Version string or null if not installed
|
|
147
|
+
*/
|
|
148
|
+
async function getToolVersion(toolName, versionFlag, verbose = false) {
|
|
149
|
+
// First check if the tool exists
|
|
150
|
+
const exists = await commandExists(toolName);
|
|
151
|
+
if (!exists) {
|
|
152
|
+
if (verbose) {
|
|
153
|
+
console.log(`[verbose] ${toolName}: not found in PATH`);
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get the version - command-stream handles the output capture
|
|
159
|
+
const result = await execCommand(`${toolName} ${versionFlag}`);
|
|
160
|
+
|
|
161
|
+
// Combine stdout and stderr since some tools output version to stderr
|
|
162
|
+
const output = `${result.stdout}\n${result.stderr}`.trim();
|
|
163
|
+
|
|
164
|
+
if (verbose) {
|
|
165
|
+
console.log(
|
|
166
|
+
`[verbose] ${toolName} ${versionFlag}: exit=${result.code}, output="${output.substring(0, 100)}"`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!output) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Return the first line of output
|
|
175
|
+
const firstLine = output.split('\n')[0];
|
|
176
|
+
return firstLine || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Run a command with real-time output streaming
|
|
181
|
+
* This returns a ProcessRunner that can be used for advanced control.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} command - The shell command to execute
|
|
184
|
+
* @param {object} options - Options for command execution
|
|
185
|
+
* @param {boolean} options.mirror - If true, mirror output to console (default: true)
|
|
186
|
+
* @param {boolean} options.capture - If true, capture output (default: true)
|
|
187
|
+
* @param {string} options.stdin - Input to pass to the command
|
|
188
|
+
* @param {string} options.cwd - Working directory
|
|
189
|
+
* @param {object} options.env - Environment variables
|
|
190
|
+
* @returns {Promise<ProcessRunner>} The process runner for the command
|
|
191
|
+
*/
|
|
192
|
+
async function runCommand(command, options = {}) {
|
|
193
|
+
const { $ } = await getCommandStream();
|
|
194
|
+
|
|
195
|
+
const $cmd = $({
|
|
196
|
+
mirror: options.mirror !== false,
|
|
197
|
+
capture: options.capture !== false,
|
|
198
|
+
stdin: options.stdin,
|
|
199
|
+
cwd: options.cwd,
|
|
200
|
+
env: options.env,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Return the process runner
|
|
204
|
+
return $cmd`${command}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Run a command with event handlers for stdout, stderr, and exit
|
|
209
|
+
*
|
|
210
|
+
* @param {string} command - The shell command to execute
|
|
211
|
+
* @param {object} handlers - Event handlers
|
|
212
|
+
* @param {function} handlers.onStdout - Called with stdout data chunks
|
|
213
|
+
* @param {function} handlers.onStderr - Called with stderr data chunks
|
|
214
|
+
* @param {function} handlers.onExit - Called when command exits with {code, stdout, stderr}
|
|
215
|
+
* @param {object} options - Additional options
|
|
216
|
+
* @param {boolean} options.mirror - If true, also mirror output to console
|
|
217
|
+
* @returns {Promise<void>}
|
|
218
|
+
*/
|
|
219
|
+
async function runWithHandlers(command, handlers = {}, options = {}) {
|
|
220
|
+
const { $ } = await getCommandStream();
|
|
221
|
+
|
|
222
|
+
const { onStdout, onStderr, onExit } = handlers;
|
|
223
|
+
|
|
224
|
+
const $cmd = $({
|
|
225
|
+
mirror: options.mirror === true,
|
|
226
|
+
capture: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const runner = $cmd`${command}`;
|
|
230
|
+
|
|
231
|
+
// Set up event handlers
|
|
232
|
+
if (onStdout) {
|
|
233
|
+
runner.on('stdout', onStdout);
|
|
234
|
+
}
|
|
235
|
+
if (onStderr) {
|
|
236
|
+
runner.on('stderr', onStderr);
|
|
237
|
+
}
|
|
238
|
+
if (onExit) {
|
|
239
|
+
runner.on('end', onExit);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Start the command
|
|
243
|
+
runner.start();
|
|
244
|
+
|
|
245
|
+
// Wait for completion
|
|
246
|
+
return await runner;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
getCommandStream,
|
|
251
|
+
execCommand,
|
|
252
|
+
execCommandAsync,
|
|
253
|
+
commandExists,
|
|
254
|
+
getCommandPath,
|
|
255
|
+
getToolVersion,
|
|
256
|
+
runCommand,
|
|
257
|
+
runWithHandlers,
|
|
258
|
+
};
|