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.
Files changed (63) hide show
  1. package/CHANGELOG.md +28 -231
  2. package/bun.lock +5 -0
  3. package/eslint.config.mjs +1 -1
  4. package/package.json +11 -6
  5. package/src/bin/cli.js +275 -137
  6. package/src/lib/args-parser.js +118 -0
  7. package/src/lib/execution-store.js +722 -0
  8. package/src/lib/isolation.js +51 -0
  9. package/src/lib/status-formatter.js +121 -0
  10. package/src/lib/version.js +143 -0
  11. package/test/args-parser.test.js +107 -0
  12. package/test/cli.test.js +11 -1
  13. package/test/docker-autoremove.test.js +11 -16
  14. package/test/execution-store.test.js +483 -0
  15. package/test/isolation-cleanup.test.js +11 -16
  16. package/test/isolation.test.js +11 -17
  17. package/test/public-exports.test.js +105 -0
  18. package/test/status-query.test.js +195 -0
  19. package/.github/workflows/release.yml +0 -352
  20. package/.husky/pre-commit +0 -1
  21. package/ARCHITECTURE.md +0 -297
  22. package/LICENSE +0 -24
  23. package/README.md +0 -339
  24. package/REQUIREMENTS.md +0 -299
  25. package/docs/PIPES.md +0 -243
  26. package/docs/USAGE.md +0 -194
  27. package/docs/case-studies/issue-15/README.md +0 -208
  28. package/docs/case-studies/issue-18/README.md +0 -343
  29. package/docs/case-studies/issue-18/issue-comments.json +0 -1
  30. package/docs/case-studies/issue-18/issue-data.json +0 -7
  31. package/docs/case-studies/issue-22/analysis.md +0 -547
  32. package/docs/case-studies/issue-22/issue-data.json +0 -12
  33. package/docs/case-studies/issue-25/README.md +0 -232
  34. package/docs/case-studies/issue-25/issue-data.json +0 -21
  35. package/docs/case-studies/issue-28/README.md +0 -405
  36. package/docs/case-studies/issue-28/issue-data.json +0 -105
  37. package/docs/case-studies/issue-28/raw-issue-data.md +0 -92
  38. package/experiments/debug-regex.js +0 -49
  39. package/experiments/isolation-design.md +0 -131
  40. package/experiments/screen-output-test.js +0 -265
  41. package/experiments/test-cli.sh +0 -42
  42. package/experiments/test-command-stream-cjs.cjs +0 -30
  43. package/experiments/test-command-stream-wrapper.js +0 -54
  44. package/experiments/test-command-stream.mjs +0 -56
  45. package/experiments/test-screen-attached.js +0 -126
  46. package/experiments/test-screen-logfile.js +0 -286
  47. package/experiments/test-screen-modes.js +0 -128
  48. package/experiments/test-screen-output.sh +0 -27
  49. package/experiments/test-screen-tee-debug.js +0 -237
  50. package/experiments/test-screen-tee-fallback.js +0 -230
  51. package/experiments/test-substitution.js +0 -143
  52. package/experiments/user-isolation-research.md +0 -83
  53. package/scripts/changeset-version.mjs +0 -38
  54. package/scripts/check-file-size.mjs +0 -103
  55. package/scripts/create-github-release.mjs +0 -93
  56. package/scripts/create-manual-changeset.mjs +0 -89
  57. package/scripts/format-github-release.mjs +0 -83
  58. package/scripts/format-release-notes.mjs +0 -219
  59. package/scripts/instant-version-bump.mjs +0 -121
  60. package/scripts/publish-to-npm.mjs +0 -129
  61. package/scripts/setup-npm.mjs +0 -37
  62. package/scripts/validate-changeset.mjs +0 -107
  63. package/scripts/version-and-commit.mjs +0 -237
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env bun
2
- /* global console */
3
- /**
4
- * Experiment: Test command-stream with CommonJS require
5
- */
6
-
7
- (async () => {
8
- console.log(
9
- '=== Testing command-stream with dynamic import in CommonJS ===\n'
10
- );
11
-
12
- // Need to use dynamic import since command-stream is ESM
13
- const { $ } = await import('command-stream');
14
-
15
- // Test 1: Basic sync command
16
- console.log('Test 1: Basic sync command');
17
- const result1 = $`echo "Hello from CJS"`.sync();
18
- console.log('stdout:', result1.stdout);
19
- console.log('exit code:', result1.code);
20
- console.log('');
21
-
22
- // Test 2: which command
23
- console.log('Test 2: which command');
24
- const result2 = $`which echo`.sync();
25
- console.log('stdout:', result2.stdout.trim());
26
- console.log('exit code:', result2.code);
27
- console.log('');
28
-
29
- console.log('=== All tests completed ===');
30
- })();
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment: Test command-stream wrapper module
4
- */
5
-
6
- const {
7
- execCommand,
8
- execCommandAsync,
9
- commandExists,
10
- getCommandPath,
11
- getToolVersion,
12
- } = require('../src/lib/command-stream');
13
-
14
- (async () => {
15
- console.log('=== Testing command-stream wrapper ===\n');
16
-
17
- // Test 1: Basic sync command
18
- console.log('Test 1: execCommand (sync-like via promise)');
19
- const result1 = await execCommand('echo "Hello from wrapper"');
20
- console.log('stdout:', result1.stdout);
21
- console.log('code:', result1.code);
22
- console.log('');
23
-
24
- // Test 2: Check if command exists
25
- console.log('Test 2: commandExists');
26
- const hasScreen = await commandExists('screen');
27
- const hasFakeCmd = await commandExists('nonexistent-command-xyz');
28
- console.log('screen exists:', hasScreen);
29
- console.log('nonexistent-command-xyz exists:', hasFakeCmd);
30
- console.log('');
31
-
32
- // Test 3: Get command path
33
- console.log('Test 3: getCommandPath');
34
- const echoPath = await getCommandPath('echo');
35
- console.log('echo path:', echoPath);
36
- console.log('');
37
-
38
- // Test 4: Get tool version
39
- console.log('Test 4: getToolVersion');
40
- const screenVersion = await getToolVersion('screen', '-v', true);
41
- console.log('screen version:', screenVersion);
42
- const tmuxVersion = await getToolVersion('tmux', '-V', true);
43
- console.log('tmux version:', tmuxVersion);
44
- console.log('');
45
-
46
- // Test 5: Async command
47
- console.log('Test 5: execCommandAsync');
48
- const result5 = await execCommandAsync('uname -r');
49
- console.log('stdout:', result5.stdout);
50
- console.log('code:', result5.code);
51
- console.log('');
52
-
53
- console.log('=== All tests completed ===');
54
- })();
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment: Test command-stream basic usage
4
- */
5
-
6
- import { $ } from 'command-stream';
7
-
8
- console.log('=== Testing command-stream ===\n');
9
-
10
- // Test 1: Basic async command
11
- console.log('Test 1: Basic async command');
12
- const result1 = await $`echo "Hello from command-stream"`;
13
- console.log('stdout:', result1.stdout);
14
- console.log('exit code:', result1.code);
15
- console.log('');
16
-
17
- // Test 2: Sync command
18
- console.log('Test 2: Sync command');
19
- const result2 = $`echo "Sync hello"`.sync();
20
- console.log('stdout:', result2.stdout);
21
- console.log('exit code:', result2.code);
22
- console.log('');
23
-
24
- // Test 3: Command that uses system tools
25
- console.log('Test 3: which command');
26
- try {
27
- const result3 = $`which echo`.sync();
28
- console.log('stdout:', result3.stdout);
29
- console.log('exit code:', result3.code);
30
- } catch (e) {
31
- console.log('Error:', e.message);
32
- }
33
- console.log('');
34
-
35
- // Test 4: Get OS version on Linux
36
- console.log('Test 4: uname command');
37
- const result4 = $`uname -r`.sync();
38
- console.log('stdout:', result4.stdout.trim());
39
- console.log('exit code:', result4.code);
40
- console.log('');
41
-
42
- // Test 5: Silent mode (no mirror)
43
- console.log('Test 5: Silent mode with $({ mirror: false })');
44
- const $silent = $({ mirror: false, capture: true });
45
- const result5 = await $silent`echo "This should be silent"`;
46
- console.log('stdout:', result5.stdout);
47
- console.log('');
48
-
49
- // Test 6: Check if command exists
50
- console.log('Test 6: Check if screen exists');
51
- const result6 = $`which screen`.sync();
52
- console.log('screen exists:', result6.code === 0);
53
- console.log('screen path:', result6.stdout.trim());
54
- console.log('');
55
-
56
- console.log('=== All tests completed ===');
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment to test different approaches for running screen in attached mode
4
- * from Bun without a TTY
5
- */
6
-
7
- const { spawn, spawnSync, execSync } = require('child_process');
8
-
9
- async function testApproaches() {
10
- console.log('=== Testing Attached Mode Approaches ===\n');
11
-
12
- // Approach 1: Using script command as wrapper
13
- console.log('Approach 1: script -q -c "screen ..." /dev/null');
14
- try {
15
- const sessionName = `approach1-${Date.now()}`;
16
- const result = spawnSync(
17
- 'script',
18
- [
19
- '-q',
20
- '-c',
21
- `screen -S ${sessionName} /bin/sh -c "echo hello; exit 0"`,
22
- '/dev/null',
23
- ],
24
- {
25
- stdio: ['inherit', 'pipe', 'pipe'],
26
- timeout: 5000,
27
- }
28
- );
29
- console.log(
30
- ` stdout: "${result.stdout.toString().trim().replace(/\n/g, '\\n')}"`
31
- );
32
- console.log(
33
- ` stderr: "${result.stderr.toString().trim().replace(/\n/g, '\\n')}"`
34
- );
35
- console.log(` exit: ${result.status}`);
36
- // Cleanup
37
- try {
38
- execSync(`screen -S ${sessionName} -X quit 2>/dev/null`);
39
- } catch {}
40
- } catch (e) {
41
- console.log(` Error: ${e.message}`);
42
- }
43
- console.log('');
44
-
45
- // Approach 2: Using detached mode + wait for completion + capture output via log
46
- console.log('Approach 2: detached + log capture');
47
- try {
48
- const sessionName = `approach2-${Date.now()}`;
49
- const logFile = `/tmp/screen-${sessionName}.log`;
50
-
51
- // Start with logging
52
- execSync(
53
- `screen -dmS ${sessionName} -L -Logfile ${logFile} /bin/sh -c "echo 'hello from approach2'"`,
54
- {
55
- stdio: 'inherit',
56
- }
57
- );
58
-
59
- // Wait for completion
60
- await new Promise((r) => setTimeout(r, 500));
61
-
62
- // Read log
63
- const output = execSync(`cat ${logFile}`, { encoding: 'utf8' });
64
- console.log(` Output: "${output.trim()}"`);
65
- console.log(` Status: SUCCESS`);
66
-
67
- // Cleanup
68
- try {
69
- execSync(`rm ${logFile} 2>/dev/null`);
70
- } catch {}
71
- try {
72
- execSync(`screen -S ${sessionName} -X quit 2>/dev/null`);
73
- } catch {}
74
- } catch (e) {
75
- console.log(` Error: ${e.message}`);
76
- }
77
- console.log('');
78
-
79
- // Approach 3: Using stdio: 'inherit' with proper terminal allocation via script
80
- console.log('Approach 3: Run through script, inherit all stdio');
81
- try {
82
- const sessionName = `approach3-${Date.now()}`;
83
- const result = spawnSync(
84
- 'script',
85
- [
86
- '-q',
87
- '-e',
88
- '-c',
89
- `screen -S ${sessionName} /bin/sh -c "echo hello_approach3"`,
90
- '/dev/null',
91
- ],
92
- {
93
- stdio: 'inherit',
94
- timeout: 5000,
95
- }
96
- );
97
- console.log(` exit: ${result.status}`);
98
- // Cleanup
99
- try {
100
- execSync(`screen -S ${sessionName} -X quit 2>/dev/null`);
101
- } catch {}
102
- } catch (e) {
103
- console.log(` Error: ${e.message}`);
104
- }
105
- console.log('');
106
-
107
- // Approach 4: Direct run without screen for attached mode (fallback)
108
- console.log(
109
- 'Approach 4: Just spawn the shell command directly (fallback, no screen)'
110
- );
111
- try {
112
- const result = spawnSync('/bin/sh', ['-c', 'echo hello_direct'], {
113
- stdio: ['inherit', 'pipe', 'pipe'],
114
- timeout: 5000,
115
- });
116
- console.log(` stdout: "${result.stdout.toString().trim()}"`);
117
- console.log(` exit: ${result.status}`);
118
- } catch (e) {
119
- console.log(` Error: ${e.message}`);
120
- }
121
- console.log('');
122
-
123
- console.log('=== Approaches Tested ===');
124
- }
125
-
126
- testApproaches();
@@ -1,286 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment to test screen's logfile capture functionality
4
- * to understand the root cause of issue #15
5
- */
6
-
7
- const { execSync, spawnSync } = require('child_process');
8
- const fs = require('fs');
9
- const path = require('path');
10
- const os = require('os');
11
-
12
- async function sleep(ms) {
13
- return new Promise((resolve) => setTimeout(resolve, ms));
14
- }
15
-
16
- async function testScreenLogfile() {
17
- console.log('=== Testing Screen Logfile Capture ===\n');
18
-
19
- // Test environment info
20
- console.log('Environment:');
21
- console.log(` Platform: ${process.platform}`);
22
- console.log(` Node: ${process.version}`);
23
- try {
24
- const screenVersion = execSync('screen --version', {
25
- encoding: 'utf8',
26
- }).trim();
27
- console.log(` Screen: ${screenVersion}`);
28
- } catch (e) {
29
- console.log(` Screen: Not available - ${e.message}`);
30
- return;
31
- }
32
- console.log(
33
- ` TTY: stdin=${process.stdin.isTTY}, stdout=${process.stdout.isTTY}`
34
- );
35
- console.log('');
36
-
37
- // Test 1: Basic logfile capture with -L -Logfile
38
- console.log('Test 1: Basic -L -Logfile capture');
39
- const sessionName1 = `logtest-${Date.now()}`;
40
- const logFile1 = path.join(os.tmpdir(), `screen-log-${sessionName1}.log`);
41
-
42
- try {
43
- // Run screen with logging
44
- const screenArgs = [
45
- '-dmS',
46
- sessionName1,
47
- '-L',
48
- '-Logfile',
49
- logFile1,
50
- '/bin/sh',
51
- '-c',
52
- 'echo "TESTOUTPUT123"',
53
- ];
54
-
55
- console.log(` Command: screen ${screenArgs.join(' ')}`);
56
-
57
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
58
- stdio: 'inherit',
59
- });
60
-
61
- // Wait for completion (screen runs command and exits)
62
- await sleep(500);
63
-
64
- // Check if session still exists
65
- let sessionExists = false;
66
- try {
67
- const sessions = execSync('screen -ls', {
68
- encoding: 'utf8',
69
- stdio: ['pipe', 'pipe', 'pipe'],
70
- });
71
- sessionExists = sessions.includes(sessionName1);
72
- } catch {
73
- // screen -ls returns non-zero if no sessions
74
- }
75
- console.log(` Session exists after 500ms: ${sessionExists}`);
76
-
77
- // Check log file
78
- if (fs.existsSync(logFile1)) {
79
- const content = fs.readFileSync(logFile1, 'utf8');
80
- console.log(` Log file exists: YES`);
81
- console.log(` Log file size: ${content.length} bytes`);
82
- console.log(
83
- ` Log content: "${content.trim().replace(/\n/g, '\\n').slice(0, 200)}"`
84
- );
85
- console.log(
86
- ` Contains expected output: ${content.includes('TESTOUTPUT123') ? 'YES ✓' : 'NO ✗'}`
87
- );
88
- fs.unlinkSync(logFile1);
89
- } else {
90
- console.log(` Log file exists: NO ✗`);
91
- console.log(` Expected path: ${logFile1}`);
92
- }
93
-
94
- // Cleanup
95
- try {
96
- execSync(`screen -S ${sessionName1} -X quit 2>/dev/null`);
97
- } catch {}
98
- } catch (e) {
99
- console.log(` Error: ${e.message}`);
100
- }
101
- console.log('');
102
-
103
- // Test 2: Test with sleep to ensure buffer flush
104
- console.log('Test 2: With sleep for buffer flush');
105
- const sessionName2 = `logtest2-${Date.now()}`;
106
- const logFile2 = path.join(os.tmpdir(), `screen-log-${sessionName2}.log`);
107
-
108
- try {
109
- const screenArgs = [
110
- '-dmS',
111
- sessionName2,
112
- '-L',
113
- '-Logfile',
114
- logFile2,
115
- '/bin/sh',
116
- '-c',
117
- 'echo "FLUSHED_OUTPUT" && sleep 0.5',
118
- ];
119
-
120
- console.log(` Command: screen ${screenArgs.join(' ')}`);
121
-
122
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
123
- stdio: 'inherit',
124
- });
125
-
126
- // Wait longer for flush (default is 10 seconds)
127
- await sleep(1500);
128
-
129
- // Check log file
130
- if (fs.existsSync(logFile2)) {
131
- const content = fs.readFileSync(logFile2, 'utf8');
132
- console.log(` Log file exists: YES`);
133
- console.log(` Log file size: ${content.length} bytes`);
134
- console.log(
135
- ` Contains expected output: ${content.includes('FLUSHED_OUTPUT') ? 'YES ✓' : 'NO ✗'}`
136
- );
137
- fs.unlinkSync(logFile2);
138
- } else {
139
- console.log(` Log file exists: NO ✗`);
140
- }
141
-
142
- // Cleanup
143
- try {
144
- execSync(`screen -S ${sessionName2} -X quit 2>/dev/null`);
145
- } catch {}
146
- } catch (e) {
147
- console.log(` Error: ${e.message}`);
148
- }
149
- console.log('');
150
-
151
- // Test 3: Alternative - using hardstatus/output redirection
152
- console.log('Test 3: Direct command output capture (no screen logging)');
153
- const sessionName3 = `logtest3-${Date.now()}`;
154
- const logFile3 = path.join(os.tmpdir(), `screen-log-${sessionName3}.log`);
155
-
156
- try {
157
- // Run command through screen but capture output to file within the command
158
- const command = `echo "DIRECT_CAPTURE" | tee ${logFile3}`;
159
- const screenArgs = ['-dmS', sessionName3, '/bin/sh', '-c', command];
160
-
161
- console.log(` Command: screen ${screenArgs.join(' ')}`);
162
-
163
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
164
- stdio: 'inherit',
165
- });
166
-
167
- await sleep(500);
168
-
169
- // Check log file
170
- if (fs.existsSync(logFile3)) {
171
- const content = fs.readFileSync(logFile3, 'utf8');
172
- console.log(` Log file exists: YES`);
173
- console.log(
174
- ` Contains expected output: ${content.includes('DIRECT_CAPTURE') ? 'YES ✓' : 'NO ✗'}`
175
- );
176
- fs.unlinkSync(logFile3);
177
- } else {
178
- console.log(` Log file exists: NO ✗`);
179
- }
180
-
181
- // Cleanup
182
- try {
183
- execSync(`screen -S ${sessionName3} -X quit 2>/dev/null`);
184
- } catch {}
185
- } catch (e) {
186
- console.log(` Error: ${e.message}`);
187
- }
188
- console.log('');
189
-
190
- // Test 4: Script command approach
191
- console.log('Test 4: Using script command to capture output');
192
- const sessionName4 = `logtest4-${Date.now()}`;
193
- const logFile4 = path.join(os.tmpdir(), `script-log-${sessionName4}.log`);
194
-
195
- try {
196
- // Use script to capture output
197
- const result = spawnSync(
198
- 'script',
199
- [
200
- '-q',
201
- logFile4,
202
- '-c',
203
- `screen -dmS ${sessionName4} /bin/sh -c "echo SCRIPT_CAPTURE"`,
204
- ],
205
- {
206
- stdio: ['inherit', 'pipe', 'pipe'],
207
- timeout: 5000,
208
- }
209
- );
210
-
211
- await sleep(500);
212
-
213
- console.log(` Exit code: ${result.status}`);
214
-
215
- // Check log file
216
- if (fs.existsSync(logFile4)) {
217
- const content = fs.readFileSync(logFile4, 'utf8');
218
- console.log(` Log file exists: YES`);
219
- console.log(` Log file size: ${content.length} bytes`);
220
- fs.unlinkSync(logFile4);
221
- } else {
222
- console.log(` Log file exists: NO`);
223
- }
224
-
225
- // Cleanup
226
- try {
227
- execSync(`screen -S ${sessionName4} -X quit 2>/dev/null`);
228
- } catch {}
229
- } catch (e) {
230
- console.log(` Error: ${e.message}`);
231
- }
232
- console.log('');
233
-
234
- // Test 5: Test with -T option (terminal type)
235
- console.log('Test 5: With explicit terminal type -T xterm');
236
- const sessionName5 = `logtest5-${Date.now()}`;
237
- const logFile5 = path.join(os.tmpdir(), `screen-log-${sessionName5}.log`);
238
-
239
- try {
240
- const screenArgs = [
241
- '-T',
242
- 'xterm',
243
- '-dmS',
244
- sessionName5,
245
- '-L',
246
- '-Logfile',
247
- logFile5,
248
- '/bin/sh',
249
- '-c',
250
- 'echo "TERMINAL_OUTPUT" && sleep 0.3',
251
- ];
252
-
253
- console.log(` Command: screen ${screenArgs.join(' ')}`);
254
-
255
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
256
- stdio: 'inherit',
257
- });
258
-
259
- await sleep(1000);
260
-
261
- // Check log file
262
- if (fs.existsSync(logFile5)) {
263
- const content = fs.readFileSync(logFile5, 'utf8');
264
- console.log(` Log file exists: YES`);
265
- console.log(` Log file size: ${content.length} bytes`);
266
- console.log(
267
- ` Contains expected output: ${content.includes('TERMINAL_OUTPUT') ? 'YES ✓' : 'NO ✗'}`
268
- );
269
- fs.unlinkSync(logFile5);
270
- } else {
271
- console.log(` Log file exists: NO ✗`);
272
- }
273
-
274
- // Cleanup
275
- try {
276
- execSync(`screen -S ${sessionName5} -X quit 2>/dev/null`);
277
- } catch {}
278
- } catch (e) {
279
- console.log(` Error: ${e.message}`);
280
- }
281
- console.log('');
282
-
283
- console.log('=== Tests Complete ===');
284
- }
285
-
286
- testScreenLogfile();
@@ -1,128 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment to test different screen invocation modes
4
- * This helps understand how screen behaves in different contexts
5
- */
6
-
7
- const { spawn, execSync, spawnSync } = require('child_process');
8
-
9
- async function testScreenModes() {
10
- console.log('=== Testing Screen Modes ===\n');
11
-
12
- // Test 1: Check if running in a terminal
13
- console.log('1. Terminal check:');
14
- console.log(` process.stdin.isTTY: ${process.stdin.isTTY}`);
15
- console.log(` process.stdout.isTTY: ${process.stdout.isTTY}`);
16
- console.log(` TERM: ${process.env.TERM || 'not set'}`);
17
- console.log('');
18
-
19
- // Test 2: Detached mode should work
20
- console.log('2. Testing detached mode (screen -dmS):');
21
- try {
22
- const sessionName = `test-${Date.now()}`;
23
- execSync(
24
- `screen -dmS ${sessionName} /bin/sh -c "echo hello > /tmp/screen-test-${sessionName}.txt"`,
25
- {
26
- stdio: 'inherit',
27
- }
28
- );
29
- // Wait a bit for the command to complete
30
- await new Promise((r) => setTimeout(r, 200));
31
-
32
- const output = execSync(
33
- `cat /tmp/screen-test-${sessionName}.txt 2>/dev/null || echo "file not found"`,
34
- { encoding: 'utf8' }
35
- );
36
- console.log(` Output: ${output.trim()}`);
37
-
38
- // Cleanup
39
- try {
40
- execSync(`screen -S ${sessionName} -X quit 2>/dev/null`);
41
- } catch {}
42
- try {
43
- execSync(`rm /tmp/screen-test-${sessionName}.txt 2>/dev/null`);
44
- } catch {}
45
-
46
- console.log(' Status: SUCCESS');
47
- } catch (e) {
48
- console.log(` Status: FAILED - ${e.message}`);
49
- }
50
- console.log('');
51
-
52
- // Test 3: Attached mode with spawn (current implementation)
53
- console.log('3. Testing attached mode with spawn (current implementation):');
54
- try {
55
- const sessionName = `test-${Date.now()}`;
56
- const result = spawnSync(
57
- 'screen',
58
- ['-S', sessionName, '/bin/sh', '-c', 'echo hello'],
59
- {
60
- stdio: 'inherit',
61
- }
62
- );
63
- console.log(` Exit code: ${result.status}`);
64
- console.log(` Status: ${result.status === 0 ? 'SUCCESS' : 'FAILED'}`);
65
- } catch (e) {
66
- console.log(` Status: FAILED - ${e.message}`);
67
- }
68
- console.log('');
69
-
70
- // Test 4: Try to use script command to allocate PTY
71
- console.log('4. Testing with script command to allocate PTY:');
72
- try {
73
- const result = spawnSync(
74
- 'script',
75
- ['-q', '-c', 'echo hello', '/dev/null'],
76
- {
77
- stdio: ['inherit', 'pipe', 'pipe'],
78
- }
79
- );
80
- console.log(` Output: ${result.stdout.toString().trim()}`);
81
- console.log(` Exit code: ${result.status}`);
82
- console.log(` Status: SUCCESS`);
83
- } catch (e) {
84
- console.log(` Status: FAILED - ${e.message}`);
85
- }
86
- console.log('');
87
-
88
- // Test 5: Detached mode with log capture
89
- console.log('5. Testing detached mode with log capture:');
90
- try {
91
- const sessionName = `test-${Date.now()}`;
92
- const logFile = `/tmp/screen-log-${sessionName}.txt`;
93
-
94
- // Create detached session with logging
95
- execSync(
96
- `screen -dmS ${sessionName} -L -Logfile ${logFile} /bin/sh -c "echo 'hello from screen'; sleep 0.2"`,
97
- {
98
- stdio: 'inherit',
99
- }
100
- );
101
-
102
- // Wait for command to complete
103
- await new Promise((r) => setTimeout(r, 500));
104
-
105
- const output = execSync(
106
- `cat ${logFile} 2>/dev/null || echo "log not found"`,
107
- { encoding: 'utf8' }
108
- );
109
- console.log(` Log content: ${output.trim().replace(/\n/g, '\\n')}`);
110
-
111
- // Cleanup
112
- try {
113
- execSync(`screen -S ${sessionName} -X quit 2>/dev/null`);
114
- } catch {}
115
- try {
116
- execSync(`rm ${logFile} 2>/dev/null`);
117
- } catch {}
118
-
119
- console.log(' Status: SUCCESS');
120
- } catch (e) {
121
- console.log(` Status: FAILED - ${e.message}`);
122
- }
123
- console.log('');
124
-
125
- console.log('=== Tests Complete ===');
126
- }
127
-
128
- testScreenModes();