start-command 0.13.0 → 0.16.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 (65) hide show
  1. package/CHANGELOG.md +34 -227
  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 +332 -171
  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/output-blocks.js +357 -0
  10. package/src/lib/status-formatter.js +148 -0
  11. package/src/lib/version.js +143 -0
  12. package/test/args-parser.test.js +107 -0
  13. package/test/cli.test.js +11 -1
  14. package/test/docker-autoremove.test.js +11 -16
  15. package/test/execution-store.test.js +483 -0
  16. package/test/isolation-cleanup.test.js +11 -16
  17. package/test/isolation.test.js +11 -17
  18. package/test/output-blocks.test.js +197 -0
  19. package/test/public-exports.test.js +105 -0
  20. package/test/status-query.test.js +197 -0
  21. package/.github/workflows/release.yml +0 -352
  22. package/.husky/pre-commit +0 -1
  23. package/ARCHITECTURE.md +0 -297
  24. package/LICENSE +0 -24
  25. package/README.md +0 -339
  26. package/REQUIREMENTS.md +0 -299
  27. package/docs/PIPES.md +0 -243
  28. package/docs/USAGE.md +0 -194
  29. package/docs/case-studies/issue-15/README.md +0 -208
  30. package/docs/case-studies/issue-18/README.md +0 -343
  31. package/docs/case-studies/issue-18/issue-comments.json +0 -1
  32. package/docs/case-studies/issue-18/issue-data.json +0 -7
  33. package/docs/case-studies/issue-22/analysis.md +0 -547
  34. package/docs/case-studies/issue-22/issue-data.json +0 -12
  35. package/docs/case-studies/issue-25/README.md +0 -232
  36. package/docs/case-studies/issue-25/issue-data.json +0 -21
  37. package/docs/case-studies/issue-28/README.md +0 -405
  38. package/docs/case-studies/issue-28/issue-data.json +0 -105
  39. package/docs/case-studies/issue-28/raw-issue-data.md +0 -92
  40. package/experiments/debug-regex.js +0 -49
  41. package/experiments/isolation-design.md +0 -131
  42. package/experiments/screen-output-test.js +0 -265
  43. package/experiments/test-cli.sh +0 -42
  44. package/experiments/test-command-stream-cjs.cjs +0 -30
  45. package/experiments/test-command-stream-wrapper.js +0 -54
  46. package/experiments/test-command-stream.mjs +0 -56
  47. package/experiments/test-screen-attached.js +0 -126
  48. package/experiments/test-screen-logfile.js +0 -286
  49. package/experiments/test-screen-modes.js +0 -128
  50. package/experiments/test-screen-output.sh +0 -27
  51. package/experiments/test-screen-tee-debug.js +0 -237
  52. package/experiments/test-screen-tee-fallback.js +0 -230
  53. package/experiments/test-substitution.js +0 -143
  54. package/experiments/user-isolation-research.md +0 -83
  55. package/scripts/changeset-version.mjs +0 -38
  56. package/scripts/check-file-size.mjs +0 -103
  57. package/scripts/create-github-release.mjs +0 -93
  58. package/scripts/create-manual-changeset.mjs +0 -89
  59. package/scripts/format-github-release.mjs +0 -83
  60. package/scripts/format-release-notes.mjs +0 -219
  61. package/scripts/instant-version-bump.mjs +0 -121
  62. package/scripts/publish-to-npm.mjs +0 -129
  63. package/scripts/setup-npm.mjs +0 -37
  64. package/scripts/validate-changeset.mjs +0 -107
  65. package/scripts/version-and-commit.mjs +0 -237
@@ -1,27 +0,0 @@
1
- #!/bin/bash
2
- # Experiment to understand screen output behavior
3
-
4
- echo "=== Test 1: Direct screen with command ==="
5
- screen -S test1 /bin/sh -c 'echo "hello from test1"'
6
-
7
- echo ""
8
- echo "=== Test 2: Screen with -L logging ==="
9
- cd /tmp
10
- screen -L -Logfile screen-test.log -S test2 /bin/sh -c 'echo "hello from test2"'
11
- echo "Log contents:"
12
- cat /tmp/screen-test.log 2>/dev/null || echo "No log file created"
13
-
14
- echo ""
15
- echo "=== Test 3: Screen detached then capture ==="
16
- screen -dmS test3 /bin/sh -c 'echo "hello from test3" > /tmp/screen-test3-out.txt'
17
- sleep 0.5
18
- echo "Output from test3:"
19
- cat /tmp/screen-test3-out.txt 2>/dev/null || echo "No output file"
20
-
21
- echo ""
22
- echo "=== Test 4: Screen with wrap using script command ==="
23
- script -q /dev/null -c 'screen -S test4 /bin/sh -c "echo hello from test4"' 2>/dev/null || echo "Script method failed"
24
-
25
- echo ""
26
- echo "Cleanup"
27
- rm -f /tmp/screen-test.log /tmp/screen-test3-out.txt
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Debug experiment to find the root cause of issue #25
4
- * We're testing different ways of running screen commands with tee
5
- */
6
-
7
- const { execSync, spawn } = 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 testDebug() {
17
- console.log('=== Debugging Screen Tee Fallback ===\n');
18
-
19
- // Test 1: Simple command without quotes issues
20
- console.log('Test 1: Simple command (no spaces in the command)');
21
- const sessionName1 = `debug1-${Date.now()}`;
22
- const logFile1 = path.join(os.tmpdir(), `debug1-${sessionName1}.log`);
23
-
24
- try {
25
- // Direct screen command with tee
26
- const cmd = `screen -dmS "${sessionName1}" /bin/sh -c "(echo hello) 2>&1 | tee \\"${logFile1}\\""`;
27
- console.log(` Command: ${cmd}`);
28
-
29
- execSync(cmd, { stdio: 'inherit' });
30
- await sleep(500);
31
-
32
- if (fs.existsSync(logFile1)) {
33
- console.log(
34
- ` Log content: "${fs.readFileSync(logFile1, 'utf8').trim()}"`
35
- );
36
- console.log(` Result: SUCCESS ✓`);
37
- fs.unlinkSync(logFile1);
38
- } else {
39
- console.log(` Result: FAILED - No log file ✗`);
40
- }
41
- } catch (e) {
42
- console.log(` Error: ${e.message}`);
43
- }
44
- console.log('');
45
-
46
- // Test 2: Command with nested quotes
47
- console.log('Test 2: Command with "hello" (has quotes)');
48
- const sessionName2 = `debug2-${Date.now()}`;
49
- const logFile2 = path.join(os.tmpdir(), `debug2-${sessionName2}.log`);
50
-
51
- try {
52
- // Note: The original command has quotes: echo "hello"
53
- // When we wrap it with tee, the quoting becomes complex
54
- const cmd = `screen -dmS "${sessionName2}" /bin/sh -c "(echo \\"hello\\") 2>&1 | tee \\"${logFile2}\\""`;
55
- console.log(` Command: ${cmd}`);
56
-
57
- execSync(cmd, { stdio: 'inherit' });
58
- await sleep(500);
59
-
60
- if (fs.existsSync(logFile2)) {
61
- console.log(
62
- ` Log content: "${fs.readFileSync(logFile2, 'utf8').trim()}"`
63
- );
64
- console.log(` Result: SUCCESS ✓`);
65
- fs.unlinkSync(logFile2);
66
- } else {
67
- console.log(` Result: FAILED - No log file ✗`);
68
- }
69
- } catch (e) {
70
- console.log(` Error: ${e.message}`);
71
- }
72
- console.log('');
73
-
74
- // Test 3: Using array-based command (like the current implementation)
75
- console.log('Test 3: Using array-based args (current implementation style)');
76
- const sessionName3 = `debug3-${Date.now()}`;
77
- const logFile3 = path.join(os.tmpdir(), `debug3-${sessionName3}.log`);
78
-
79
- try {
80
- // This is what the current code does
81
- const command = 'echo "hello"';
82
- const effectiveCommand = `(${command}) 2>&1 | tee "${logFile3}"`;
83
- const screenArgs = [
84
- '-dmS',
85
- sessionName3,
86
- '/bin/sh',
87
- '-c',
88
- effectiveCommand,
89
- ];
90
-
91
- // Construct the command string as the code does
92
- const cmdStr = `screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`;
93
- console.log(` Constructed command: ${cmdStr}`);
94
-
95
- execSync(cmdStr, { stdio: 'inherit' });
96
- await sleep(500);
97
-
98
- if (fs.existsSync(logFile3)) {
99
- console.log(
100
- ` Log content: "${fs.readFileSync(logFile3, 'utf8').trim()}"`
101
- );
102
- console.log(` Result: SUCCESS ✓`);
103
- fs.unlinkSync(logFile3);
104
- } else {
105
- console.log(` Result: FAILED - No log file ✗`);
106
- }
107
- } catch (e) {
108
- console.log(` Error: ${e.message}`);
109
- }
110
- console.log('');
111
-
112
- // Test 4: Check what happens with the nested quotes
113
- console.log('Test 4: Checking quote escaping issue');
114
- const sessionName4 = `debug4-${Date.now()}`;
115
- const logFile4 = path.join(os.tmpdir(), `debug4-${sessionName4}.log`);
116
-
117
- try {
118
- const command = 'echo "hello from attached mode"';
119
- const effectiveCommand = `(${command}) 2>&1 | tee "${logFile4}"`;
120
- console.log(` effectiveCommand: ${effectiveCommand}`);
121
-
122
- // When we quote each arg with `"${a}"`, the command becomes double-quoted
123
- // which can cause issues with nested quotes
124
- const screenArgs = [
125
- '-dmS',
126
- sessionName4,
127
- '/bin/sh',
128
- '-c',
129
- effectiveCommand,
130
- ];
131
- const cmdStr = `screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`;
132
- console.log(` Full command: ${cmdStr}`);
133
-
134
- // The problem: effectiveCommand has double quotes inside,
135
- // and we're wrapping it with MORE double quotes
136
- // This results in: screen "-dmS" "debug4-xxx" "/bin/sh" "-c" "(echo "hello from attached mode") 2>&1 | tee "...""
137
- // The nested double quotes break the shell parsing!
138
-
139
- execSync(cmdStr, { stdio: 'inherit' });
140
- await sleep(500);
141
-
142
- if (fs.existsSync(logFile4)) {
143
- console.log(
144
- ` Log content: "${fs.readFileSync(logFile4, 'utf8').trim()}"`
145
- );
146
- console.log(` Result: SUCCESS ✓`);
147
- fs.unlinkSync(logFile4);
148
- } else {
149
- console.log(` Result: FAILED - No log file ✗`);
150
- }
151
- } catch (e) {
152
- console.log(` Error: ${e.message}`);
153
- }
154
- console.log('');
155
-
156
- // Test 5: Proper escaping
157
- console.log('Test 5: With proper quote escaping');
158
- const sessionName5 = `debug5-${Date.now()}`;
159
- const logFile5 = path.join(os.tmpdir(), `debug5-${sessionName5}.log`);
160
-
161
- try {
162
- const command = 'echo "hello from attached mode"';
163
- // Escape the inner quotes
164
- const escapedCommand = command.replace(/"/g, '\\"');
165
- const effectiveCommand = `(${escapedCommand}) 2>&1 | tee "${logFile5}"`;
166
- console.log(` effectiveCommand: ${effectiveCommand}`);
167
-
168
- const screenArgs = [
169
- '-dmS',
170
- sessionName5,
171
- '/bin/sh',
172
- '-c',
173
- effectiveCommand,
174
- ];
175
- const cmdStr = `screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`;
176
- console.log(` Full command: ${cmdStr}`);
177
-
178
- execSync(cmdStr, { stdio: 'inherit' });
179
- await sleep(500);
180
-
181
- if (fs.existsSync(logFile5)) {
182
- console.log(
183
- ` Log content: "${fs.readFileSync(logFile5, 'utf8').trim()}"`
184
- );
185
- console.log(` Result: SUCCESS ✓`);
186
- fs.unlinkSync(logFile5);
187
- } else {
188
- console.log(` Result: FAILED - No log file ✗`);
189
- }
190
- } catch (e) {
191
- console.log(` Error: ${e.message}`);
192
- }
193
- console.log('');
194
-
195
- // Test 6: Use spawnSync instead of execSync with constructed string
196
- console.log('Test 6: Using spawnSync with array (better approach)');
197
- const sessionName6 = `debug6-${Date.now()}`;
198
- const logFile6 = path.join(os.tmpdir(), `debug6-${sessionName6}.log`);
199
-
200
- try {
201
- const command = 'echo "hello from attached mode"';
202
- const effectiveCommand = `(${command}) 2>&1 | tee "${logFile6}"`;
203
- console.log(` effectiveCommand: ${effectiveCommand}`);
204
-
205
- const { spawnSync } = require('child_process');
206
- const screenArgs = [
207
- '-dmS',
208
- sessionName6,
209
- '/bin/sh',
210
- '-c',
211
- effectiveCommand,
212
- ];
213
- console.log(` spawnSync args: screen ${screenArgs.join(' ')}`);
214
-
215
- const result = spawnSync('screen', screenArgs, { stdio: 'inherit' });
216
- console.log(` spawnSync exit code: ${result.status}`);
217
-
218
- await sleep(500);
219
-
220
- if (fs.existsSync(logFile6)) {
221
- console.log(
222
- ` Log content: "${fs.readFileSync(logFile6, 'utf8').trim()}"`
223
- );
224
- console.log(` Result: SUCCESS ✓`);
225
- fs.unlinkSync(logFile6);
226
- } else {
227
- console.log(` Result: FAILED - No log file ✗`);
228
- }
229
- } catch (e) {
230
- console.log(` Error: ${e.message}`);
231
- }
232
- console.log('');
233
-
234
- console.log('=== Debug Tests Complete ===');
235
- }
236
-
237
- testDebug();
@@ -1,230 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Experiment to test screen's tee fallback functionality
4
- * This simulates the behavior on macOS with older screen (< 4.5.1)
5
- * which doesn't support -Logfile option
6
- *
7
- * Issue #25: We don't get `Hello` output from `$ --isolated screen --verbose -- echo "hello"` command
8
- */
9
-
10
- const { execSync, spawnSync, spawn } = require('child_process');
11
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
-
15
- async function sleep(ms) {
16
- return new Promise((resolve) => setTimeout(resolve, ms));
17
- }
18
-
19
- async function testTeeFallback() {
20
- console.log('=== Testing Screen Tee Fallback (macOS 4.0.3 simulation) ===\n');
21
-
22
- // Test environment info
23
- console.log('Environment:');
24
- console.log(` Platform: ${process.platform}`);
25
- console.log(` Node: ${process.version}`);
26
- try {
27
- const screenVersion = execSync('screen --version', {
28
- encoding: 'utf8',
29
- }).trim();
30
- console.log(` Screen: ${screenVersion}`);
31
- } catch (e) {
32
- console.log(` Screen: Not available - ${e.message}`);
33
- return;
34
- }
35
- console.log(
36
- ` TTY: stdin=${process.stdin.isTTY}, stdout=${process.stdout.isTTY}`
37
- );
38
- console.log('');
39
-
40
- // Test 1: The tee fallback approach (current implementation for macOS)
41
- console.log('Test 1: Tee fallback approach (current implementation)');
42
- const sessionName1 = `tee-test-${Date.now()}`;
43
- const logFile1 = path.join(os.tmpdir(), `screen-tee-${sessionName1}.log`);
44
- const command = 'echo "hello"';
45
-
46
- try {
47
- // This is the current implementation for older screen versions
48
- const effectiveCommand = `(${command}) 2>&1 | tee "${logFile1}"`;
49
- const shell = '/bin/sh';
50
- const shellArg = '-c';
51
- const screenArgs = [
52
- '-dmS',
53
- sessionName1,
54
- shell,
55
- shellArg,
56
- effectiveCommand,
57
- ];
58
-
59
- console.log(` Command: screen ${screenArgs.join(' ')}`);
60
- console.log(` Effective command inside screen: ${effectiveCommand}`);
61
-
62
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
63
- stdio: 'inherit',
64
- });
65
-
66
- // Wait for completion and poll for session
67
- let waited = 0;
68
- const maxWait = 5000;
69
- const interval = 100;
70
-
71
- while (waited < maxWait) {
72
- await sleep(interval);
73
- waited += interval;
74
-
75
- try {
76
- const sessions = execSync('screen -ls', {
77
- encoding: 'utf8',
78
- stdio: ['pipe', 'pipe', 'pipe'],
79
- });
80
- if (!sessions.includes(sessionName1)) {
81
- console.log(` Session ended after ${waited}ms`);
82
- break;
83
- }
84
- } catch {
85
- // screen -ls returns non-zero if no sessions
86
- console.log(` Session ended after ${waited}ms (no sessions)`);
87
- break;
88
- }
89
- }
90
-
91
- // Check log file
92
- if (fs.existsSync(logFile1)) {
93
- const content = fs.readFileSync(logFile1, 'utf8');
94
- console.log(` Log file exists: YES`);
95
- console.log(` Log file size: ${content.length} bytes`);
96
- console.log(` Log content: "${content.trim()}"`);
97
- console.log(
98
- ` Contains expected output: ${content.includes('hello') ? 'YES ✓' : 'NO ✗'}`
99
- );
100
- fs.unlinkSync(logFile1);
101
- } else {
102
- console.log(` Log file exists: NO ✗`);
103
- console.log(` Expected path: ${logFile1}`);
104
- }
105
-
106
- // Cleanup
107
- try {
108
- execSync(`screen -S ${sessionName1} -X quit 2>/dev/null`);
109
- } catch {}
110
- } catch (e) {
111
- console.log(` Error: ${e.message}`);
112
- }
113
- console.log('');
114
-
115
- // Test 2: Test the attached mode WITHOUT TTY (hasTTY() returns false)
116
- console.log(
117
- 'Test 2: Simulating attached mode without TTY (current code path)'
118
- );
119
- const sessionName2 = `tee-notty-${Date.now()}`;
120
- const logFile2 = path.join(os.tmpdir(), `screen-tee-${sessionName2}.log`);
121
-
122
- try {
123
- // Simulate the runScreenWithLogCapture function behavior for older screen
124
- const command2 = 'echo "hello from attached mode"';
125
- const effectiveCommand2 = `(${command2}) 2>&1 | tee "${logFile2}"`;
126
- const shell = '/bin/sh';
127
- const shellArg = '-c';
128
- const screenArgs = [
129
- '-dmS',
130
- sessionName2,
131
- shell,
132
- shellArg,
133
- effectiveCommand2,
134
- ];
135
-
136
- console.log(` Screen args: ${screenArgs.join(' ')}`);
137
-
138
- execSync(`screen ${screenArgs.map((a) => `"${a}"`).join(' ')}`, {
139
- stdio: 'inherit',
140
- });
141
-
142
- // Poll for session completion (as done in current implementation)
143
- const checkInterval = 100;
144
- const maxWait = 5000;
145
- let waited = 0;
146
-
147
- const checkCompletion = async () => {
148
- while (waited < maxWait) {
149
- await sleep(checkInterval);
150
- waited += checkInterval;
151
-
152
- try {
153
- const sessions = execSync('screen -ls', {
154
- encoding: 'utf8',
155
- stdio: ['pipe', 'pipe', 'pipe'],
156
- });
157
- if (!sessions.includes(sessionName2)) {
158
- break;
159
- }
160
- } catch {
161
- break;
162
- }
163
- }
164
- };
165
-
166
- await checkCompletion();
167
- console.log(` Session ended after ${waited}ms`);
168
-
169
- // Read output
170
- if (fs.existsSync(logFile2)) {
171
- const content = fs.readFileSync(logFile2, 'utf8');
172
- console.log(` Log file exists: YES`);
173
- console.log(` Log content: "${content.trim()}"`);
174
- console.log(
175
- ` Contains expected: ${content.includes('hello from attached mode') ? 'YES ✓' : 'NO ✗'}`
176
- );
177
- fs.unlinkSync(logFile2);
178
- } else {
179
- console.log(` Log file exists: NO ✗`);
180
- }
181
-
182
- try {
183
- execSync(`screen -S ${sessionName2} -X quit 2>/dev/null`);
184
- } catch {}
185
- } catch (e) {
186
- console.log(` Error: ${e.message}`);
187
- }
188
- console.log('');
189
-
190
- // Test 3: What happens if we have a TTY?
191
- console.log('Test 3: Test with attached mode WITH TTY (spawn with inherit)');
192
- const sessionName3 = `tty-test-${Date.now()}`;
193
-
194
- try {
195
- const command3 = 'echo "hello TTY"';
196
- const screenArgs = ['-S', sessionName3, '/bin/sh', '-c', command3];
197
-
198
- console.log(` Screen args for attached mode: ${screenArgs.join(' ')}`);
199
- console.log(
200
- ` hasTTY: stdin=${process.stdin.isTTY}, stdout=${process.stdout.isTTY}`
201
- );
202
-
203
- // This mimics what the current code does when hasTTY() returns true
204
- return new Promise((resolve) => {
205
- const child = spawn('screen', screenArgs, {
206
- stdio: 'inherit',
207
- });
208
-
209
- child.on('exit', (code) => {
210
- console.log(` Exit code: ${code}`);
211
- console.log(
212
- ` Note: In attached mode with TTY, output goes directly to terminal`
213
- );
214
- resolve();
215
- });
216
-
217
- child.on('error', (err) => {
218
- console.log(` Error: ${err.message}`);
219
- resolve();
220
- });
221
- });
222
- } catch (e) {
223
- console.log(` Error: ${e.message}`);
224
- }
225
- console.log('');
226
-
227
- console.log('=== Tests Complete ===');
228
- }
229
-
230
- testTeeFallback();
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Test script for the substitution engine
4
- * Tests pattern matching with various inputs
5
- */
6
-
7
- const path = require('path');
8
-
9
- // Import the substitution module
10
- const {
11
- parseLinoContent,
12
- matchAndSubstitute,
13
- loadDefaultSubstitutions,
14
- processCommand,
15
- } = require('../src/lib/substitution');
16
-
17
- const testCases = [
18
- // NPM install patterns
19
- {
20
- input: 'install gh-upload-log npm package',
21
- expected: 'npm install gh-upload-log',
22
- description: 'Basic npm install',
23
- },
24
- {
25
- input: 'install 0.0.1 version of gh-upload-log npm package',
26
- expected: 'npm install gh-upload-log@0.0.1',
27
- description: 'npm install with version',
28
- },
29
- {
30
- input: 'install lodash npm package globally',
31
- expected: 'npm install -g lodash',
32
- description: 'Global npm install',
33
- },
34
- {
35
- input: 'install 4.17.21 version of lodash npm package globally',
36
- expected: 'npm install -g lodash@4.17.21',
37
- description: 'Global npm install with version',
38
- },
39
- {
40
- input: 'uninstall lodash npm package',
41
- expected: 'npm uninstall lodash',
42
- description: 'npm uninstall',
43
- },
44
-
45
- // Git patterns
46
- {
47
- input: 'clone https://github.com/user/repo repository',
48
- expected: 'git clone https://github.com/user/repo',
49
- description: 'Git clone with URL',
50
- },
51
- {
52
- input: 'clone git@github.com:user/repo.git repository',
53
- expected: 'git clone git@github.com:user/repo.git',
54
- description: 'Git clone with SSH URL',
55
- },
56
- {
57
- input: 'checkout main branch',
58
- expected: 'git checkout main',
59
- description: 'Git checkout branch',
60
- },
61
- {
62
- input: 'create feature-x branch',
63
- expected: 'git checkout -b feature-x',
64
- description: 'Git create branch',
65
- },
66
-
67
- // Common operations
68
- {
69
- input: 'list files',
70
- expected: 'ls -la',
71
- description: 'List files',
72
- },
73
- {
74
- input: 'show current directory',
75
- expected: 'pwd',
76
- description: 'Show working directory',
77
- },
78
- {
79
- input: 'create my-project directory',
80
- expected: 'mkdir -p my-project',
81
- description: 'Create directory',
82
- },
83
-
84
- // Python patterns
85
- {
86
- input: 'install requests python package',
87
- expected: 'pip install requests',
88
- description: 'pip install',
89
- },
90
-
91
- // Non-matching patterns (should return original)
92
- {
93
- input: 'echo hello world',
94
- expected: 'echo hello world',
95
- description: 'Non-matching command (pass through)',
96
- },
97
- {
98
- input: 'npm test',
99
- expected: 'npm test',
100
- description: 'Regular npm command (pass through)',
101
- },
102
- ];
103
-
104
- console.log('=== Substitution Engine Tests ===\n');
105
-
106
- // Load default substitutions
107
- const rules = loadDefaultSubstitutions();
108
- console.log(`Loaded ${rules.length} substitution rules\n`);
109
-
110
- let passed = 0;
111
- let failed = 0;
112
-
113
- for (const test of testCases) {
114
- const result = matchAndSubstitute(test.input, rules);
115
-
116
- if (result.command === test.expected) {
117
- console.log(`✓ PASS: ${test.description}`);
118
- console.log(` Input: "${test.input}"`);
119
- console.log(` Output: "${result.command}"`);
120
- console.log(
121
- ` Matched: ${result.matched ? result.rule.pattern : 'none (pass through)'}`
122
- );
123
- passed++;
124
- } else {
125
- console.log(`✗ FAIL: ${test.description}`);
126
- console.log(` Input: "${test.input}"`);
127
- console.log(` Expected: "${test.expected}"`);
128
- console.log(` Got: "${result.command}"`);
129
- console.log(` Matched: ${result.matched ? result.rule.pattern : 'none'}`);
130
- failed++;
131
- }
132
- console.log('');
133
- }
134
-
135
- console.log('=== Summary ===');
136
- console.log(`Passed: ${passed}/${testCases.length}`);
137
- console.log(`Failed: ${failed}/${testCases.length}`);
138
-
139
- if (failed > 0) {
140
- process.exit(1);
141
- }
142
-
143
- console.log('\n=== All tests passed! ===');