start-command 0.17.2 → 0.17.3
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 +15 -0
- package/package.json +1 -1
- package/src/bin/cli.js +37 -112
- package/src/lib/spawn-helpers.js +346 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# start-command
|
|
2
2
|
|
|
3
|
+
## 0.17.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a61f1a9: fix: Use Bun.spawn for reliable stdout capture on macOS (Issue #57)
|
|
8
|
+
|
|
9
|
+
The previous fix (v0.17.2) using `close` event instead of `exit` did not resolve the issue on macOS. After deeper investigation, we discovered the root cause: Bun's event loop may exit before the `close` event callback can be scheduled, especially for fast commands like `echo`.
|
|
10
|
+
|
|
11
|
+
This fix uses Bun's native `Bun.spawn` API with async/await for stream handling when running on Bun runtime. This approach keeps the event loop alive until all streams are consumed and the process exits.
|
|
12
|
+
- Use `Bun.spawn` instead of `node:child_process` when running on Bun
|
|
13
|
+
- Use async stream readers with `getReader()` for real-time output display
|
|
14
|
+
- Use `await proc.exited` to ensure process completion before exiting
|
|
15
|
+
- Fall back to `node:child_process` with `close` event for Node.js compatibility
|
|
16
|
+
- Add verbose logging with `--verbose` flag for debugging
|
|
17
|
+
|
|
3
18
|
## 0.17.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process');
|
|
4
3
|
const process = require('process');
|
|
5
4
|
const os = require('os');
|
|
6
5
|
const fs = require('fs');
|
|
@@ -35,6 +34,7 @@ const { ExecutionStore, ExecutionRecord } = require('../lib/execution-store');
|
|
|
35
34
|
const { queryStatus } = require('../lib/status-formatter');
|
|
36
35
|
const { printVersion } = require('../lib/version');
|
|
37
36
|
const { createStartBlock, createFinishBlock } = require('../lib/output-blocks');
|
|
37
|
+
const { runWithBunSpawn, runWithNodeSpawn } = require('../lib/spawn-helpers');
|
|
38
38
|
|
|
39
39
|
// Configuration from environment variables
|
|
40
40
|
const config = {
|
|
@@ -529,11 +529,18 @@ async function runWithIsolation(
|
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
/**
|
|
532
|
-
* Run command directly (without isolation)
|
|
532
|
+
* Run command directly (without isolation)
|
|
533
|
+
*
|
|
534
|
+
* Uses Bun.spawn when running on Bun for reliable event handling on macOS.
|
|
535
|
+
* Falls back to node:child_process for Node.js compatibility.
|
|
536
|
+
*
|
|
537
|
+
* Issue #57: On macOS with Bun, node:child_process events may not fire reliably
|
|
538
|
+
* before the event loop exits. Bun.spawn provides more reliable stream handling.
|
|
539
|
+
*
|
|
533
540
|
* @param {string} cmd - Command to execute
|
|
534
541
|
* @param {string} sessionId - Session UUID for tracking
|
|
535
542
|
*/
|
|
536
|
-
function runDirect(cmd, sessionId) {
|
|
543
|
+
async function runDirect(cmd, sessionId) {
|
|
537
544
|
// Get the command name (first word of the actual command to execute)
|
|
538
545
|
const commandName = cmd.split(' ')[0];
|
|
539
546
|
|
|
@@ -606,76 +613,8 @@ function runDirect(cmd, sessionId) {
|
|
|
606
613
|
);
|
|
607
614
|
console.log('');
|
|
608
615
|
|
|
609
|
-
//
|
|
610
|
-
const
|
|
611
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
612
|
-
shell: false,
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
// Update execution record with PID and save initial state
|
|
616
|
-
if (executionRecord && store) {
|
|
617
|
-
executionRecord.pid = child.pid;
|
|
618
|
-
try {
|
|
619
|
-
store.save(executionRecord);
|
|
620
|
-
} catch (err) {
|
|
621
|
-
if (config.verbose) {
|
|
622
|
-
console.error(
|
|
623
|
-
`[Tracking] Warning: Could not save execution record: ${err.message}`
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Capture stdout
|
|
630
|
-
child.stdout.on('data', (data) => {
|
|
631
|
-
const text = data.toString();
|
|
632
|
-
process.stdout.write(text);
|
|
633
|
-
logContent += text;
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
// Capture stderr
|
|
637
|
-
child.stderr.on('data', (data) => {
|
|
638
|
-
const text = data.toString();
|
|
639
|
-
process.stderr.write(text);
|
|
640
|
-
logContent += text;
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// Handle process close (not 'exit' - we need to wait for all stdio to be closed)
|
|
644
|
-
// The 'close' event fires after all stdio streams have been closed, ensuring
|
|
645
|
-
// all stdout/stderr data has been received. The 'exit' event can fire before
|
|
646
|
-
// buffered data is received, causing output loss on macOS (Issue #57).
|
|
647
|
-
child.on('close', (code) => {
|
|
648
|
-
const exitCode = code || 0;
|
|
649
|
-
const endTime = getTimestamp();
|
|
650
|
-
|
|
651
|
-
// Log footer
|
|
652
|
-
logContent += `\n${'='.repeat(50)}\n`;
|
|
653
|
-
logContent += `Finished: ${endTime}\n`;
|
|
654
|
-
logContent += `Exit Code: ${exitCode}\n`;
|
|
655
|
-
|
|
656
|
-
// Write log file
|
|
657
|
-
try {
|
|
658
|
-
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
659
|
-
} catch (err) {
|
|
660
|
-
console.error(`\nWarning: Could not save log file: ${err.message}`);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Update execution record as completed
|
|
664
|
-
if (executionRecord && store) {
|
|
665
|
-
executionRecord.complete(exitCode);
|
|
666
|
-
try {
|
|
667
|
-
store.save(executionRecord);
|
|
668
|
-
} catch (err) {
|
|
669
|
-
if (config.verbose) {
|
|
670
|
-
console.error(
|
|
671
|
-
`[Tracking] Warning: Could not update execution record: ${err.message}`
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Print finish block
|
|
678
|
-
const durationMs = Date.now() - startTimeMs;
|
|
616
|
+
// Completion callback
|
|
617
|
+
const onComplete = (exitCode, endTime, _logContent, durationMs) => {
|
|
679
618
|
console.log('');
|
|
680
619
|
console.log(
|
|
681
620
|
createFinishBlock({
|
|
@@ -686,47 +625,14 @@ function runDirect(cmd, sessionId) {
|
|
|
686
625
|
durationMs,
|
|
687
626
|
})
|
|
688
627
|
);
|
|
689
|
-
|
|
690
|
-
// If command failed, try to auto-report
|
|
691
628
|
if (exitCode !== 0) {
|
|
692
629
|
handleFailure(config, commandName, cmd, exitCode, logFilePath);
|
|
693
630
|
}
|
|
694
|
-
|
|
695
631
|
process.exit(exitCode);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Handle spawn errors
|
|
699
|
-
child.on('error', (err) => {
|
|
700
|
-
const endTime = getTimestamp();
|
|
701
|
-
const durationMs = Date.now() - startTimeMs;
|
|
702
|
-
const errorMessage = `Error executing command: ${err.message}`;
|
|
703
|
-
|
|
704
|
-
logContent += `\n${errorMessage}\n`;
|
|
705
|
-
logContent += `\n${'='.repeat(50)}\n`;
|
|
706
|
-
logContent += `Finished: ${endTime}\n`;
|
|
707
|
-
logContent += `Exit Code: 1\n`;
|
|
708
|
-
|
|
709
|
-
// Write log file
|
|
710
|
-
try {
|
|
711
|
-
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
712
|
-
} catch (writeErr) {
|
|
713
|
-
console.error(`\nWarning: Could not save log file: ${writeErr.message}`);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Update execution record as failed
|
|
717
|
-
if (executionRecord && store) {
|
|
718
|
-
executionRecord.complete(1);
|
|
719
|
-
try {
|
|
720
|
-
store.save(executionRecord);
|
|
721
|
-
} catch (storeErr) {
|
|
722
|
-
if (config.verbose) {
|
|
723
|
-
console.error(
|
|
724
|
-
`[Tracking] Warning: Could not update execution record: ${storeErr.message}`
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
632
|
+
};
|
|
729
633
|
|
|
634
|
+
// Error callback
|
|
635
|
+
const onError = (errorMessage, endTime, durationMs) => {
|
|
730
636
|
console.error(`\n${errorMessage}`);
|
|
731
637
|
console.log('');
|
|
732
638
|
console.log(
|
|
@@ -738,11 +644,30 @@ function runDirect(cmd, sessionId) {
|
|
|
738
644
|
durationMs,
|
|
739
645
|
})
|
|
740
646
|
);
|
|
741
|
-
|
|
742
647
|
handleFailure(config, commandName, cmd, 1, logFilePath);
|
|
743
|
-
|
|
744
648
|
process.exit(1);
|
|
745
|
-
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Use Bun.spawn when running on Bun for reliable event handling on macOS
|
|
652
|
+
// Fall back to node:child_process for Node.js compatibility
|
|
653
|
+
const spawnOptions = {
|
|
654
|
+
shell,
|
|
655
|
+
shellArgs,
|
|
656
|
+
logFilePath,
|
|
657
|
+
logContent,
|
|
658
|
+
startTimeMs,
|
|
659
|
+
executionRecord,
|
|
660
|
+
store,
|
|
661
|
+
config,
|
|
662
|
+
onComplete,
|
|
663
|
+
onError,
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
if (typeof Bun !== 'undefined') {
|
|
667
|
+
await runWithBunSpawn(spawnOptions);
|
|
668
|
+
} else {
|
|
669
|
+
runWithNodeSpawn(spawnOptions);
|
|
670
|
+
}
|
|
746
671
|
}
|
|
747
672
|
|
|
748
673
|
/**
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn helper functions for reliable cross-platform command execution
|
|
3
|
+
*
|
|
4
|
+
* Issue #57: On macOS with Bun, node:child_process events may not fire reliably
|
|
5
|
+
* before the event loop exits. Bun.spawn provides more reliable stream handling.
|
|
6
|
+
*
|
|
7
|
+
* This module provides two implementations:
|
|
8
|
+
* - runWithBunSpawn: Uses Bun.spawn with async/await for reliable event handling
|
|
9
|
+
* - runWithNodeSpawn: Uses node:child_process with close event for Node.js compatibility
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { spawn } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run command using Bun.spawn (for Bun runtime)
|
|
17
|
+
* Uses async/await for reliable stream handling on macOS
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} options - Execution options
|
|
20
|
+
* @param {string} options.shell - Shell to use
|
|
21
|
+
* @param {string[]} options.shellArgs - Shell arguments
|
|
22
|
+
* @param {string} options.logFilePath - Path to log file
|
|
23
|
+
* @param {string} options.logContent - Initial log content
|
|
24
|
+
* @param {number} options.startTimeMs - Start timestamp
|
|
25
|
+
* @param {Object} options.executionRecord - Execution tracking record
|
|
26
|
+
* @param {Object} options.store - Execution store
|
|
27
|
+
* @param {Object} options.config - CLI configuration
|
|
28
|
+
* @param {Function} options.onComplete - Callback for completion (exitCode, endTime, logContent, durationMs)
|
|
29
|
+
* @param {Function} options.onError - Callback for errors (errorMessage, endTime, durationMs)
|
|
30
|
+
*/
|
|
31
|
+
async function runWithBunSpawn(options) {
|
|
32
|
+
const {
|
|
33
|
+
shell,
|
|
34
|
+
shellArgs,
|
|
35
|
+
logFilePath,
|
|
36
|
+
startTimeMs,
|
|
37
|
+
executionRecord,
|
|
38
|
+
store,
|
|
39
|
+
config,
|
|
40
|
+
onComplete,
|
|
41
|
+
onError,
|
|
42
|
+
} = options;
|
|
43
|
+
|
|
44
|
+
let logContent = options.logContent || '';
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Spawn the process using Bun's native API
|
|
48
|
+
const proc = Bun.spawn([shell, ...shellArgs], {
|
|
49
|
+
stdout: 'pipe',
|
|
50
|
+
stderr: 'pipe',
|
|
51
|
+
stdin: 'inherit',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Update execution record with PID and save initial state
|
|
55
|
+
if (executionRecord && store) {
|
|
56
|
+
executionRecord.pid = proc.pid;
|
|
57
|
+
try {
|
|
58
|
+
store.save(executionRecord);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (config && config.verbose) {
|
|
61
|
+
console.error(
|
|
62
|
+
`[Tracking] Warning: Could not save execution record: ${err.message}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (config && config.verbose) {
|
|
69
|
+
console.log(`[verbose] Using Bun.spawn for reliable macOS handling`);
|
|
70
|
+
console.log(`[verbose] Process PID: ${proc.pid}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Read stdout and stderr streams concurrently
|
|
74
|
+
// TextDecoder is a global in modern runtimes (Bun, Node.js 16+)
|
|
75
|
+
// eslint-disable-next-line no-undef
|
|
76
|
+
const decoder = new TextDecoder();
|
|
77
|
+
|
|
78
|
+
// Read stdout in real-time
|
|
79
|
+
const stdoutReader = proc.stdout.getReader();
|
|
80
|
+
const readStdout = async () => {
|
|
81
|
+
let output = '';
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const { done, value } = await stdoutReader.read();
|
|
85
|
+
if (done) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
const text = decoder.decode(value);
|
|
89
|
+
process.stdout.write(text);
|
|
90
|
+
output += text;
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (config && config.verbose) {
|
|
94
|
+
console.error(`[verbose] stdout read error: ${err.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return output;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Read stderr in real-time
|
|
101
|
+
const stderrReader = proc.stderr.getReader();
|
|
102
|
+
const readStderr = async () => {
|
|
103
|
+
let output = '';
|
|
104
|
+
try {
|
|
105
|
+
while (true) {
|
|
106
|
+
const { done, value } = await stderrReader.read();
|
|
107
|
+
if (done) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const text = decoder.decode(value);
|
|
111
|
+
process.stderr.write(text);
|
|
112
|
+
output += text;
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (config && config.verbose) {
|
|
116
|
+
console.error(`[verbose] stderr read error: ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return output;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Read both streams concurrently and wait for process to exit
|
|
123
|
+
const [stdoutContent, stderrContent, exitCode] = await Promise.all([
|
|
124
|
+
readStdout(),
|
|
125
|
+
readStderr(),
|
|
126
|
+
proc.exited,
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
// Add captured output to log content
|
|
130
|
+
logContent += stdoutContent;
|
|
131
|
+
logContent += stderrContent;
|
|
132
|
+
|
|
133
|
+
const durationMs = Date.now() - startTimeMs;
|
|
134
|
+
const endTime = new Date().toISOString().replace('T', ' ').substring(0, 23);
|
|
135
|
+
|
|
136
|
+
// Write log file
|
|
137
|
+
try {
|
|
138
|
+
logContent += `\n${'='.repeat(50)}\n`;
|
|
139
|
+
logContent += `Finished: ${endTime}\n`;
|
|
140
|
+
logContent += `Exit Code: ${exitCode}\n`;
|
|
141
|
+
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(`\nWarning: Could not save log file: ${err.message}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Update execution record as completed
|
|
147
|
+
if (executionRecord && store) {
|
|
148
|
+
executionRecord.complete(exitCode);
|
|
149
|
+
try {
|
|
150
|
+
store.save(executionRecord);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (config && config.verbose) {
|
|
153
|
+
console.error(
|
|
154
|
+
`[Tracking] Warning: Could not update execution record: ${err.message}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Call completion callback
|
|
161
|
+
if (onComplete) {
|
|
162
|
+
onComplete(exitCode, endTime, logContent, durationMs);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return exitCode;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
const durationMs = Date.now() - startTimeMs;
|
|
168
|
+
const endTime = new Date().toISOString().replace('T', ' ').substring(0, 23);
|
|
169
|
+
const errorMessage = `Error executing command: ${err.message}`;
|
|
170
|
+
|
|
171
|
+
logContent += `\n${errorMessage}\n`;
|
|
172
|
+
logContent += `\n${'='.repeat(50)}\n`;
|
|
173
|
+
logContent += `Finished: ${endTime}\n`;
|
|
174
|
+
logContent += `Exit Code: 1\n`;
|
|
175
|
+
|
|
176
|
+
// Write log file
|
|
177
|
+
try {
|
|
178
|
+
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
179
|
+
} catch (writeErr) {
|
|
180
|
+
console.error(`\nWarning: Could not save log file: ${writeErr.message}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update execution record as failed
|
|
184
|
+
if (executionRecord && store) {
|
|
185
|
+
executionRecord.complete(1);
|
|
186
|
+
try {
|
|
187
|
+
store.save(executionRecord);
|
|
188
|
+
} catch (storeErr) {
|
|
189
|
+
if (config && config.verbose) {
|
|
190
|
+
console.error(
|
|
191
|
+
`[Tracking] Warning: Could not update execution record: ${storeErr.message}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Call error callback
|
|
198
|
+
if (onError) {
|
|
199
|
+
onError(errorMessage, endTime, durationMs);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Run command using node:child_process (for Node.js compatibility)
|
|
208
|
+
* Uses event-based handling with close event
|
|
209
|
+
*
|
|
210
|
+
* @param {Object} options - Execution options (same as runWithBunSpawn)
|
|
211
|
+
*/
|
|
212
|
+
function runWithNodeSpawn(options) {
|
|
213
|
+
const {
|
|
214
|
+
shell,
|
|
215
|
+
shellArgs,
|
|
216
|
+
logFilePath,
|
|
217
|
+
startTimeMs,
|
|
218
|
+
executionRecord,
|
|
219
|
+
store,
|
|
220
|
+
config,
|
|
221
|
+
onComplete,
|
|
222
|
+
onError,
|
|
223
|
+
} = options;
|
|
224
|
+
|
|
225
|
+
let logContent = options.logContent || '';
|
|
226
|
+
|
|
227
|
+
// Execute the command with captured output
|
|
228
|
+
const child = spawn(shell, shellArgs, {
|
|
229
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
230
|
+
shell: false,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Update execution record with PID and save initial state
|
|
234
|
+
if (executionRecord && store) {
|
|
235
|
+
executionRecord.pid = child.pid;
|
|
236
|
+
try {
|
|
237
|
+
store.save(executionRecord);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
if (config && config.verbose) {
|
|
240
|
+
console.error(
|
|
241
|
+
`[Tracking] Warning: Could not save execution record: ${err.message}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Capture stdout
|
|
248
|
+
child.stdout.on('data', (data) => {
|
|
249
|
+
const text = data.toString();
|
|
250
|
+
process.stdout.write(text);
|
|
251
|
+
logContent += text;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Capture stderr
|
|
255
|
+
child.stderr.on('data', (data) => {
|
|
256
|
+
const text = data.toString();
|
|
257
|
+
process.stderr.write(text);
|
|
258
|
+
logContent += text;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Handle process close (not 'exit' - we need to wait for all stdio to be closed)
|
|
262
|
+
// The 'close' event fires after all stdio streams have been closed, ensuring
|
|
263
|
+
// all stdout/stderr data has been received. The 'exit' event can fire before
|
|
264
|
+
// buffered data is received, causing output loss on macOS (Issue #57).
|
|
265
|
+
child.on('close', (code) => {
|
|
266
|
+
const exitCode = code || 0;
|
|
267
|
+
const durationMs = Date.now() - startTimeMs;
|
|
268
|
+
const endTime = new Date().toISOString().replace('T', ' ').substring(0, 23);
|
|
269
|
+
|
|
270
|
+
// Log footer
|
|
271
|
+
logContent += `\n${'='.repeat(50)}\n`;
|
|
272
|
+
logContent += `Finished: ${endTime}\n`;
|
|
273
|
+
logContent += `Exit Code: ${exitCode}\n`;
|
|
274
|
+
|
|
275
|
+
// Write log file
|
|
276
|
+
try {
|
|
277
|
+
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.error(`\nWarning: Could not save log file: ${err.message}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Update execution record as completed
|
|
283
|
+
if (executionRecord && store) {
|
|
284
|
+
executionRecord.complete(exitCode);
|
|
285
|
+
try {
|
|
286
|
+
store.save(executionRecord);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (config && config.verbose) {
|
|
289
|
+
console.error(
|
|
290
|
+
`[Tracking] Warning: Could not update execution record: ${err.message}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Call completion callback
|
|
297
|
+
if (onComplete) {
|
|
298
|
+
onComplete(exitCode, endTime, logContent, durationMs);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Handle spawn errors
|
|
303
|
+
child.on('error', (err) => {
|
|
304
|
+
const durationMs = Date.now() - startTimeMs;
|
|
305
|
+
const endTime = new Date().toISOString().replace('T', ' ').substring(0, 23);
|
|
306
|
+
const errorMessage = `Error executing command: ${err.message}`;
|
|
307
|
+
|
|
308
|
+
logContent += `\n${errorMessage}\n`;
|
|
309
|
+
logContent += `\n${'='.repeat(50)}\n`;
|
|
310
|
+
logContent += `Finished: ${endTime}\n`;
|
|
311
|
+
logContent += `Exit Code: 1\n`;
|
|
312
|
+
|
|
313
|
+
// Write log file
|
|
314
|
+
try {
|
|
315
|
+
fs.writeFileSync(logFilePath, logContent, 'utf8');
|
|
316
|
+
} catch (writeErr) {
|
|
317
|
+
console.error(`\nWarning: Could not save log file: ${writeErr.message}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Update execution record as failed
|
|
321
|
+
if (executionRecord && store) {
|
|
322
|
+
executionRecord.complete(1);
|
|
323
|
+
try {
|
|
324
|
+
store.save(executionRecord);
|
|
325
|
+
} catch (storeErr) {
|
|
326
|
+
if (config && config.verbose) {
|
|
327
|
+
console.error(
|
|
328
|
+
`[Tracking] Warning: Could not update execution record: ${storeErr.message}`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Call error callback
|
|
335
|
+
if (onError) {
|
|
336
|
+
onError(errorMessage, endTime, durationMs);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return child;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = {
|
|
344
|
+
runWithBunSpawn,
|
|
345
|
+
runWithNodeSpawn,
|
|
346
|
+
};
|