command-stream 0.9.0 → 0.9.2
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/js/src/$.ansi.mjs +147 -0
- package/js/src/$.mjs +49 -6382
- package/js/src/$.process-runner-base.mjs +563 -0
- package/js/src/$.process-runner-execution.mjs +1497 -0
- package/js/src/$.process-runner-orchestration.mjs +250 -0
- package/js/src/$.process-runner-pipeline.mjs +1162 -0
- package/js/src/$.process-runner-stream-kill.mjs +312 -0
- package/js/src/$.process-runner-virtual.mjs +297 -0
- package/js/src/$.quote.mjs +161 -0
- package/js/src/$.result.mjs +23 -0
- package/js/src/$.shell-settings.mjs +84 -0
- package/js/src/$.shell.mjs +157 -0
- package/js/src/$.state.mjs +401 -0
- package/js/src/$.stream-emitter.mjs +111 -0
- package/js/src/$.stream-utils.mjs +390 -0
- package/js/src/$.trace.mjs +36 -0
- package/js/src/$.utils.mjs +2 -23
- package/js/src/$.virtual-commands.mjs +113 -0
- package/js/src/commands/$.which.mjs +3 -1
- package/js/src/commands/index.mjs +24 -0
- package/js/src/shell-parser.mjs +125 -83
- package/js/tests/resource-cleanup-internals.test.mjs +22 -24
- package/js/tests/sigint-cleanup.test.mjs +3 -0
- package/package.json +1 -1
- package/rust/src/ansi.rs +194 -0
- package/rust/src/events.rs +305 -0
- package/rust/src/lib.rs +71 -60
- package/rust/src/macros.rs +165 -0
- package/rust/src/pipeline.rs +411 -0
- package/rust/src/quote.rs +161 -0
- package/rust/src/state.rs +333 -0
- package/rust/src/stream.rs +369 -0
- package/rust/src/trace.rs +152 -0
- package/rust/src/utils.rs +53 -158
- package/rust/tests/events.rs +207 -0
- package/rust/tests/macros.rs +77 -0
- package/rust/tests/pipeline.rs +93 -0
- package/rust/tests/state.rs +207 -0
- package/rust/tests/stream.rs +102 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// Stream utility functions for safe operations and error handling
|
|
2
|
+
// Provides cross-runtime compatible stream operations
|
|
3
|
+
|
|
4
|
+
import { trace } from './$.trace.mjs';
|
|
5
|
+
|
|
6
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
7
|
+
|
|
8
|
+
// Stream utility functions for safe operations and error handling
|
|
9
|
+
export const StreamUtils = {
|
|
10
|
+
/**
|
|
11
|
+
* Check if a stream is safe to write to
|
|
12
|
+
* @param {object} stream - The stream to check
|
|
13
|
+
* @returns {boolean} Whether the stream is writable
|
|
14
|
+
*/
|
|
15
|
+
isStreamWritable(stream) {
|
|
16
|
+
return stream && stream.writable && !stream.destroyed && !stream.closed;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add standardized error handler to stdin streams
|
|
21
|
+
* @param {object} stream - The stream to add handler to
|
|
22
|
+
* @param {string} contextName - Name for trace logging
|
|
23
|
+
* @param {function} onNonEpipeError - Optional callback for non-EPIPE errors
|
|
24
|
+
*/
|
|
25
|
+
addStdinErrorHandler(stream, contextName = 'stdin', onNonEpipeError = null) {
|
|
26
|
+
if (stream && typeof stream.on === 'function') {
|
|
27
|
+
stream.on('error', (error) => {
|
|
28
|
+
const handled = this.handleStreamError(
|
|
29
|
+
error,
|
|
30
|
+
`${contextName} error event`,
|
|
31
|
+
false
|
|
32
|
+
);
|
|
33
|
+
if (!handled && onNonEpipeError) {
|
|
34
|
+
onNonEpipeError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Safely write to a stream with comprehensive error handling
|
|
42
|
+
* @param {object} stream - The stream to write to
|
|
43
|
+
* @param {Buffer|string} data - The data to write
|
|
44
|
+
* @param {string} contextName - Name for trace logging
|
|
45
|
+
* @returns {boolean} Whether the write was successful
|
|
46
|
+
*/
|
|
47
|
+
safeStreamWrite(stream, data, contextName = 'stream') {
|
|
48
|
+
if (!this.isStreamWritable(stream)) {
|
|
49
|
+
trace(
|
|
50
|
+
'ProcessRunner',
|
|
51
|
+
() =>
|
|
52
|
+
`${contextName} write skipped - not writable | ${JSON.stringify(
|
|
53
|
+
{
|
|
54
|
+
hasStream: !!stream,
|
|
55
|
+
writable: stream?.writable,
|
|
56
|
+
destroyed: stream?.destroyed,
|
|
57
|
+
closed: stream?.closed,
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2
|
|
61
|
+
)}`
|
|
62
|
+
);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = stream.write(data);
|
|
68
|
+
trace(
|
|
69
|
+
'ProcessRunner',
|
|
70
|
+
() =>
|
|
71
|
+
`${contextName} write successful | ${JSON.stringify(
|
|
72
|
+
{
|
|
73
|
+
dataLength: data?.length || 0,
|
|
74
|
+
},
|
|
75
|
+
null,
|
|
76
|
+
2
|
|
77
|
+
)}`
|
|
78
|
+
);
|
|
79
|
+
return result;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code !== 'EPIPE') {
|
|
82
|
+
trace(
|
|
83
|
+
'ProcessRunner',
|
|
84
|
+
() =>
|
|
85
|
+
`${contextName} write error | ${JSON.stringify(
|
|
86
|
+
{
|
|
87
|
+
error: error.message,
|
|
88
|
+
code: error.code,
|
|
89
|
+
isEPIPE: false,
|
|
90
|
+
},
|
|
91
|
+
null,
|
|
92
|
+
2
|
|
93
|
+
)}`
|
|
94
|
+
);
|
|
95
|
+
throw error; // Re-throw non-EPIPE errors
|
|
96
|
+
} else {
|
|
97
|
+
trace(
|
|
98
|
+
'ProcessRunner',
|
|
99
|
+
() =>
|
|
100
|
+
`${contextName} EPIPE error (ignored) | ${JSON.stringify(
|
|
101
|
+
{
|
|
102
|
+
error: error.message,
|
|
103
|
+
code: error.code,
|
|
104
|
+
isEPIPE: true,
|
|
105
|
+
},
|
|
106
|
+
null,
|
|
107
|
+
2
|
|
108
|
+
)}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Safely end a stream with error handling
|
|
117
|
+
* @param {object} stream - The stream to end
|
|
118
|
+
* @param {string} contextName - Name for trace logging
|
|
119
|
+
* @returns {boolean} Whether the end was successful
|
|
120
|
+
*/
|
|
121
|
+
safeStreamEnd(stream, contextName = 'stream') {
|
|
122
|
+
if (!this.isStreamWritable(stream) || typeof stream.end !== 'function') {
|
|
123
|
+
trace(
|
|
124
|
+
'ProcessRunner',
|
|
125
|
+
() =>
|
|
126
|
+
`${contextName} end skipped - not available | ${JSON.stringify(
|
|
127
|
+
{
|
|
128
|
+
hasStream: !!stream,
|
|
129
|
+
hasEnd: stream && typeof stream.end === 'function',
|
|
130
|
+
writable: stream?.writable,
|
|
131
|
+
},
|
|
132
|
+
null,
|
|
133
|
+
2
|
|
134
|
+
)}`
|
|
135
|
+
);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
stream.end();
|
|
141
|
+
trace('ProcessRunner', () => `${contextName} ended successfully`);
|
|
142
|
+
return true;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.code !== 'EPIPE') {
|
|
145
|
+
trace(
|
|
146
|
+
'ProcessRunner',
|
|
147
|
+
() =>
|
|
148
|
+
`${contextName} end error | ${JSON.stringify(
|
|
149
|
+
{
|
|
150
|
+
error: error.message,
|
|
151
|
+
code: error.code,
|
|
152
|
+
},
|
|
153
|
+
null,
|
|
154
|
+
2
|
|
155
|
+
)}`
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
trace(
|
|
159
|
+
'ProcessRunner',
|
|
160
|
+
() =>
|
|
161
|
+
`${contextName} EPIPE on end (ignored) | ${JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
error: error.message,
|
|
164
|
+
code: error.code,
|
|
165
|
+
},
|
|
166
|
+
null,
|
|
167
|
+
2
|
|
168
|
+
)}`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Setup comprehensive stdin handling (error handler + safe operations)
|
|
177
|
+
* @param {object} stream - The stream to setup
|
|
178
|
+
* @param {string} contextName - Name for trace logging
|
|
179
|
+
* @returns {{ write: function, end: function, isWritable: function }}
|
|
180
|
+
*/
|
|
181
|
+
setupStdinHandling(stream, contextName = 'stdin') {
|
|
182
|
+
this.addStdinErrorHandler(stream, contextName);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
write: (data) => this.safeStreamWrite(stream, data, contextName),
|
|
186
|
+
end: () => this.safeStreamEnd(stream, contextName),
|
|
187
|
+
isWritable: () => this.isStreamWritable(stream),
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Handle stream errors with consistent EPIPE behavior
|
|
193
|
+
* @param {Error} error - The error to handle
|
|
194
|
+
* @param {string} contextName - Name for trace logging
|
|
195
|
+
* @param {boolean} shouldThrow - Whether to throw non-EPIPE errors
|
|
196
|
+
* @returns {boolean} Whether the error was an EPIPE (handled gracefully)
|
|
197
|
+
*/
|
|
198
|
+
handleStreamError(error, contextName, shouldThrow = true) {
|
|
199
|
+
if (error.code !== 'EPIPE') {
|
|
200
|
+
trace(
|
|
201
|
+
'ProcessRunner',
|
|
202
|
+
() =>
|
|
203
|
+
`${contextName} error | ${JSON.stringify(
|
|
204
|
+
{
|
|
205
|
+
error: error.message,
|
|
206
|
+
code: error.code,
|
|
207
|
+
isEPIPE: false,
|
|
208
|
+
},
|
|
209
|
+
null,
|
|
210
|
+
2
|
|
211
|
+
)}`
|
|
212
|
+
);
|
|
213
|
+
if (shouldThrow) {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
} else {
|
|
218
|
+
trace(
|
|
219
|
+
'ProcessRunner',
|
|
220
|
+
() =>
|
|
221
|
+
`${contextName} EPIPE error (ignored) | ${JSON.stringify(
|
|
222
|
+
{
|
|
223
|
+
error: error.message,
|
|
224
|
+
code: error.code,
|
|
225
|
+
isEPIPE: true,
|
|
226
|
+
},
|
|
227
|
+
null,
|
|
228
|
+
2
|
|
229
|
+
)}`
|
|
230
|
+
);
|
|
231
|
+
return true; // EPIPE handled gracefully
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Detect if stream supports Bun-style writing
|
|
237
|
+
* @param {object} stream - The stream to check
|
|
238
|
+
* @returns {boolean}
|
|
239
|
+
*/
|
|
240
|
+
isBunStream(stream) {
|
|
241
|
+
return isBun && stream && typeof stream.getWriter === 'function';
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Detect if stream supports Node.js-style writing
|
|
246
|
+
* @param {object} stream - The stream to check
|
|
247
|
+
* @returns {boolean}
|
|
248
|
+
*/
|
|
249
|
+
isNodeStream(stream) {
|
|
250
|
+
return stream && typeof stream.write === 'function';
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Write to either Bun or Node.js style stream
|
|
255
|
+
* @param {object} stream - The stream to write to
|
|
256
|
+
* @param {Buffer|string} data - The data to write
|
|
257
|
+
* @param {string} contextName - Name for trace logging
|
|
258
|
+
* @returns {Promise<boolean>} Whether the write was successful
|
|
259
|
+
*/
|
|
260
|
+
async writeToStream(stream, data, contextName = 'stream') {
|
|
261
|
+
if (this.isBunStream(stream)) {
|
|
262
|
+
try {
|
|
263
|
+
const writer = stream.getWriter();
|
|
264
|
+
await writer.write(data);
|
|
265
|
+
writer.releaseLock();
|
|
266
|
+
return true;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return this.handleStreamError(
|
|
269
|
+
error,
|
|
270
|
+
`${contextName} Bun writer`,
|
|
271
|
+
false
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
} else if (this.isNodeStream(stream)) {
|
|
275
|
+
try {
|
|
276
|
+
stream.write(data);
|
|
277
|
+
return true;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
return this.handleStreamError(
|
|
280
|
+
error,
|
|
281
|
+
`${contextName} Node writer`,
|
|
282
|
+
false
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Safe write to a stream with parent stream monitoring
|
|
292
|
+
* @param {object} stream - The stream to write to
|
|
293
|
+
* @param {Buffer|string} data - The data to write
|
|
294
|
+
* @param {object} processRunner - Optional ProcessRunner for parent stream handling
|
|
295
|
+
* @param {function} monitorParentStreams - Function to call for monitoring
|
|
296
|
+
* @returns {boolean} Whether the write was successful
|
|
297
|
+
*/
|
|
298
|
+
export function safeWrite(
|
|
299
|
+
stream,
|
|
300
|
+
data,
|
|
301
|
+
processRunner = null,
|
|
302
|
+
monitorParentStreams = null
|
|
303
|
+
) {
|
|
304
|
+
if (monitorParentStreams) {
|
|
305
|
+
monitorParentStreams();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!StreamUtils.isStreamWritable(stream)) {
|
|
309
|
+
trace(
|
|
310
|
+
'ProcessRunner',
|
|
311
|
+
() =>
|
|
312
|
+
`safeWrite skipped - stream not writable | ${JSON.stringify(
|
|
313
|
+
{
|
|
314
|
+
hasStream: !!stream,
|
|
315
|
+
writable: stream?.writable,
|
|
316
|
+
destroyed: stream?.destroyed,
|
|
317
|
+
closed: stream?.closed,
|
|
318
|
+
},
|
|
319
|
+
null,
|
|
320
|
+
2
|
|
321
|
+
)}`
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (
|
|
325
|
+
processRunner &&
|
|
326
|
+
processRunner._handleParentStreamClosure &&
|
|
327
|
+
(stream === process.stdout || stream === process.stderr)
|
|
328
|
+
) {
|
|
329
|
+
processRunner._handleParentStreamClosure();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
return stream.write(data);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
trace(
|
|
339
|
+
'ProcessRunner',
|
|
340
|
+
() =>
|
|
341
|
+
`safeWrite error | ${JSON.stringify(
|
|
342
|
+
{
|
|
343
|
+
error: error.message,
|
|
344
|
+
code: error.code,
|
|
345
|
+
writable: stream.writable,
|
|
346
|
+
destroyed: stream.destroyed,
|
|
347
|
+
},
|
|
348
|
+
null,
|
|
349
|
+
2
|
|
350
|
+
)}`
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (
|
|
354
|
+
error.code === 'EPIPE' &&
|
|
355
|
+
processRunner &&
|
|
356
|
+
processRunner._handleParentStreamClosure &&
|
|
357
|
+
(stream === process.stdout || stream === process.stderr)
|
|
358
|
+
) {
|
|
359
|
+
processRunner._handleParentStreamClosure();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Convert data to Buffer
|
|
368
|
+
* @param {Buffer|string|object} chunk - Data to convert
|
|
369
|
+
* @returns {Buffer} The data as a Buffer
|
|
370
|
+
*/
|
|
371
|
+
export function asBuffer(chunk) {
|
|
372
|
+
if (chunk === null || chunk === undefined) {
|
|
373
|
+
return Buffer.alloc(0);
|
|
374
|
+
}
|
|
375
|
+
if (Buffer.isBuffer(chunk)) {
|
|
376
|
+
return chunk;
|
|
377
|
+
}
|
|
378
|
+
if (typeof chunk === 'string') {
|
|
379
|
+
return Buffer.from(chunk, 'utf8');
|
|
380
|
+
}
|
|
381
|
+
// Handle ArrayBuffer and other views
|
|
382
|
+
if (chunk instanceof Uint8Array || chunk instanceof ArrayBuffer) {
|
|
383
|
+
return Buffer.from(chunk);
|
|
384
|
+
}
|
|
385
|
+
// Handle objects with toString
|
|
386
|
+
if (typeof chunk.toString === 'function') {
|
|
387
|
+
return Buffer.from(chunk.toString(), 'utf8');
|
|
388
|
+
}
|
|
389
|
+
return Buffer.from(String(chunk), 'utf8');
|
|
390
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Trace function for verbose logging
|
|
2
|
+
// Can be controlled via COMMAND_STREAM_VERBOSE or COMMAND_STREAM_TRACE env vars
|
|
3
|
+
// Can be disabled per-command via trace: false option
|
|
4
|
+
// CI environment no longer auto-enables tracing
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Log trace messages for debugging
|
|
8
|
+
* @param {string} category - The category of the trace message
|
|
9
|
+
* @param {string|function} messageOrFunc - The message or a function that returns the message
|
|
10
|
+
* @param {object} runner - Optional runner object to check for trace option
|
|
11
|
+
*/
|
|
12
|
+
export function trace(category, messageOrFunc, runner = null) {
|
|
13
|
+
// Check if runner explicitly disabled tracing
|
|
14
|
+
if (runner && runner.options && runner.options.trace === false) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check global trace setting (evaluated dynamically for runtime changes)
|
|
19
|
+
const TRACE_ENV = process.env.COMMAND_STREAM_TRACE;
|
|
20
|
+
const VERBOSE_ENV = process.env.COMMAND_STREAM_VERBOSE === 'true';
|
|
21
|
+
|
|
22
|
+
// COMMAND_STREAM_TRACE=false explicitly disables tracing even if COMMAND_STREAM_VERBOSE=true
|
|
23
|
+
// COMMAND_STREAM_TRACE=true explicitly enables tracing
|
|
24
|
+
// Otherwise, use COMMAND_STREAM_VERBOSE
|
|
25
|
+
const VERBOSE =
|
|
26
|
+
TRACE_ENV === 'false' ? false : TRACE_ENV === 'true' ? true : VERBOSE_ENV;
|
|
27
|
+
|
|
28
|
+
if (!VERBOSE) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const message =
|
|
33
|
+
typeof messageOrFunc === 'function' ? messageOrFunc() : messageOrFunc;
|
|
34
|
+
const timestamp = new Date().toISOString();
|
|
35
|
+
console.error(`[TRACE ${timestamp}] [${category}] ${message}`);
|
|
36
|
+
}
|
package/js/src/$.utils.mjs
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// CI environment no longer auto-enables tracing
|
|
6
|
-
export function trace(category, messageOrFunc) {
|
|
7
|
-
// Check global trace setting (evaluated dynamically for runtime changes)
|
|
8
|
-
const TRACE_ENV = process.env.COMMAND_STREAM_TRACE;
|
|
9
|
-
const VERBOSE_ENV = process.env.COMMAND_STREAM_VERBOSE === 'true';
|
|
10
|
-
|
|
11
|
-
// COMMAND_STREAM_TRACE=false explicitly disables tracing even if COMMAND_STREAM_VERBOSE=true
|
|
12
|
-
// COMMAND_STREAM_TRACE=true explicitly enables tracing
|
|
13
|
-
// Otherwise, use COMMAND_STREAM_VERBOSE
|
|
14
|
-
const VERBOSE =
|
|
15
|
-
TRACE_ENV === 'false' ? false : TRACE_ENV === 'true' ? true : VERBOSE_ENV;
|
|
16
|
-
|
|
17
|
-
if (!VERBOSE) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const message =
|
|
22
|
-
typeof messageOrFunc === 'function' ? messageOrFunc() : messageOrFunc;
|
|
23
|
-
const timestamp = new Date().toISOString();
|
|
24
|
-
console.error(`[TRACE ${timestamp}] [${category}] ${message}`);
|
|
25
|
-
}
|
|
3
|
+
// Re-export trace from the dedicated trace module for consistency
|
|
4
|
+
export { trace } from './$.trace.mjs';
|
|
26
5
|
|
|
27
6
|
export const VirtualUtils = {
|
|
28
7
|
/**
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Virtual commands registration and management
|
|
2
|
+
// Handles registration of built-in and custom virtual commands
|
|
3
|
+
|
|
4
|
+
import { trace } from './$.trace.mjs';
|
|
5
|
+
import { virtualCommands, getShellSettings } from './$.state.mjs';
|
|
6
|
+
|
|
7
|
+
// Import virtual command implementations
|
|
8
|
+
import cdCommand from './commands/$.cd.mjs';
|
|
9
|
+
import pwdCommand from './commands/$.pwd.mjs';
|
|
10
|
+
import echoCommand from './commands/$.echo.mjs';
|
|
11
|
+
import sleepCommand from './commands/$.sleep.mjs';
|
|
12
|
+
import trueCommand from './commands/$.true.mjs';
|
|
13
|
+
import falseCommand from './commands/$.false.mjs';
|
|
14
|
+
import createWhichCommand from './commands/$.which.mjs';
|
|
15
|
+
import createExitCommand from './commands/$.exit.mjs';
|
|
16
|
+
import envCommand from './commands/$.env.mjs';
|
|
17
|
+
import catCommand from './commands/$.cat.mjs';
|
|
18
|
+
import lsCommand from './commands/$.ls.mjs';
|
|
19
|
+
import mkdirCommand from './commands/$.mkdir.mjs';
|
|
20
|
+
import rmCommand from './commands/$.rm.mjs';
|
|
21
|
+
import mvCommand from './commands/$.mv.mjs';
|
|
22
|
+
import cpCommand from './commands/$.cp.mjs';
|
|
23
|
+
import touchCommand from './commands/$.touch.mjs';
|
|
24
|
+
import basenameCommand from './commands/$.basename.mjs';
|
|
25
|
+
import dirnameCommand from './commands/$.dirname.mjs';
|
|
26
|
+
import yesCommand from './commands/$.yes.mjs';
|
|
27
|
+
import seqCommand from './commands/$.seq.mjs';
|
|
28
|
+
import testCommand from './commands/$.test.mjs';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register a virtual command
|
|
32
|
+
* @param {string} name - Command name
|
|
33
|
+
* @param {function} handler - Command handler function
|
|
34
|
+
* @returns {Map} The virtual commands map
|
|
35
|
+
*/
|
|
36
|
+
export function register(name, handler) {
|
|
37
|
+
trace(
|
|
38
|
+
'VirtualCommands',
|
|
39
|
+
() => `register ENTER | ${JSON.stringify({ name }, null, 2)}`
|
|
40
|
+
);
|
|
41
|
+
virtualCommands.set(name, handler);
|
|
42
|
+
trace(
|
|
43
|
+
'VirtualCommands',
|
|
44
|
+
() => `register EXIT | ${JSON.stringify({ registered: true }, null, 2)}`
|
|
45
|
+
);
|
|
46
|
+
return virtualCommands;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Unregister a virtual command
|
|
51
|
+
* @param {string} name - Command name to remove
|
|
52
|
+
* @returns {boolean} Whether the command was removed
|
|
53
|
+
*/
|
|
54
|
+
export function unregister(name) {
|
|
55
|
+
trace(
|
|
56
|
+
'VirtualCommands',
|
|
57
|
+
() => `unregister ENTER | ${JSON.stringify({ name }, null, 2)}`
|
|
58
|
+
);
|
|
59
|
+
const deleted = virtualCommands.delete(name);
|
|
60
|
+
trace(
|
|
61
|
+
'VirtualCommands',
|
|
62
|
+
() => `unregister EXIT | ${JSON.stringify({ deleted }, null, 2)}`
|
|
63
|
+
);
|
|
64
|
+
return deleted;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* List all registered virtual commands
|
|
69
|
+
* @returns {string[]} Array of command names
|
|
70
|
+
*/
|
|
71
|
+
export function listCommands() {
|
|
72
|
+
const commands = Array.from(virtualCommands.keys());
|
|
73
|
+
trace(
|
|
74
|
+
'VirtualCommands',
|
|
75
|
+
() => `listCommands() returning ${commands.length} commands`
|
|
76
|
+
);
|
|
77
|
+
return commands;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Register all built-in virtual commands
|
|
82
|
+
*/
|
|
83
|
+
export function registerBuiltins() {
|
|
84
|
+
trace(
|
|
85
|
+
'VirtualCommands',
|
|
86
|
+
() => 'registerBuiltins() called - registering all built-in commands'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const globalShellSettings = getShellSettings();
|
|
90
|
+
|
|
91
|
+
// Register all imported commands
|
|
92
|
+
register('cd', cdCommand);
|
|
93
|
+
register('pwd', pwdCommand);
|
|
94
|
+
register('echo', echoCommand);
|
|
95
|
+
register('sleep', sleepCommand);
|
|
96
|
+
register('true', trueCommand);
|
|
97
|
+
register('false', falseCommand);
|
|
98
|
+
register('which', createWhichCommand(virtualCommands));
|
|
99
|
+
register('exit', createExitCommand(globalShellSettings));
|
|
100
|
+
register('env', envCommand);
|
|
101
|
+
register('cat', catCommand);
|
|
102
|
+
register('ls', lsCommand);
|
|
103
|
+
register('mkdir', mkdirCommand);
|
|
104
|
+
register('rm', rmCommand);
|
|
105
|
+
register('mv', mvCommand);
|
|
106
|
+
register('cp', cpCommand);
|
|
107
|
+
register('touch', touchCommand);
|
|
108
|
+
register('basename', basenameCommand);
|
|
109
|
+
register('dirname', dirnameCommand);
|
|
110
|
+
register('yes', yesCommand);
|
|
111
|
+
register('seq', seqCommand);
|
|
112
|
+
register('test', testCommand);
|
|
113
|
+
}
|
|
@@ -28,7 +28,9 @@ export default function createWhichCommand(virtualCommands) {
|
|
|
28
28
|
if (fs.statSync(fullPath).isFile()) {
|
|
29
29
|
return VirtualUtils.success(`${fullPath}\n`);
|
|
30
30
|
}
|
|
31
|
-
} catch {
|
|
31
|
+
} catch {
|
|
32
|
+
// File doesn't exist or isn't accessible, continue searching
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Command module index
|
|
2
|
+
// Re-exports all built-in virtual commands
|
|
3
|
+
|
|
4
|
+
export { default as cd } from './$.cd.mjs';
|
|
5
|
+
export { default as pwd } from './$.pwd.mjs';
|
|
6
|
+
export { default as echo } from './$.echo.mjs';
|
|
7
|
+
export { default as sleep } from './$.sleep.mjs';
|
|
8
|
+
export { default as trueCmd } from './$.true.mjs';
|
|
9
|
+
export { default as falseCmd } from './$.false.mjs';
|
|
10
|
+
export { default as createWhich } from './$.which.mjs';
|
|
11
|
+
export { default as createExit } from './$.exit.mjs';
|
|
12
|
+
export { default as env } from './$.env.mjs';
|
|
13
|
+
export { default as cat } from './$.cat.mjs';
|
|
14
|
+
export { default as ls } from './$.ls.mjs';
|
|
15
|
+
export { default as mkdir } from './$.mkdir.mjs';
|
|
16
|
+
export { default as rm } from './$.rm.mjs';
|
|
17
|
+
export { default as mv } from './$.mv.mjs';
|
|
18
|
+
export { default as cp } from './$.cp.mjs';
|
|
19
|
+
export { default as touch } from './$.touch.mjs';
|
|
20
|
+
export { default as basename } from './$.basename.mjs';
|
|
21
|
+
export { default as dirname } from './$.dirname.mjs';
|
|
22
|
+
export { default as yes } from './$.yes.mjs';
|
|
23
|
+
export { default as seq } from './$.seq.mjs';
|
|
24
|
+
export { default as test } from './$.test.mjs';
|