start-command 0.25.5 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/bin/cli.js +18 -67
- package/src/lib/args-parser.js +20 -5
- package/src/lib/status-formatter.js +108 -0
- package/src/lib/usage.js +70 -0
- package/test/args-parser.test.js +27 -3
- package/test/status-query.test.js +71 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -31,10 +31,11 @@ const {
|
|
|
31
31
|
} = require('../lib/user-manager');
|
|
32
32
|
const { handleFailure } = require('../lib/failure-handler');
|
|
33
33
|
const { ExecutionStore, ExecutionRecord } = require('../lib/execution-store');
|
|
34
|
-
const { queryStatus } = require('../lib/status-formatter');
|
|
34
|
+
const { queryStatus, listExecutions } = require('../lib/status-formatter');
|
|
35
35
|
const { printVersion } = require('../lib/version');
|
|
36
36
|
const { createStartBlock, createFinishBlock } = require('../lib/output-blocks');
|
|
37
37
|
const { runWithBunSpawn, runWithNodeSpawn } = require('../lib/spawn-helpers');
|
|
38
|
+
const { printUsage } = require('../lib/usage');
|
|
38
39
|
|
|
39
40
|
// Configuration from environment variables
|
|
40
41
|
const config = {
|
|
@@ -193,6 +194,12 @@ if (wrapperOptions.status) {
|
|
|
193
194
|
process.exit(0);
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
// Handle --list flag
|
|
198
|
+
if (wrapperOptions.list) {
|
|
199
|
+
handleListQuery(wrapperOptions.outputFormat);
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
196
203
|
// Handle --cleanup flag
|
|
197
204
|
if (wrapperOptions.cleanup) {
|
|
198
205
|
handleCleanup(wrapperOptions.cleanupDryRun);
|
|
@@ -267,6 +274,16 @@ function handleStatusQuery(uuid, outputFormat) {
|
|
|
267
274
|
}
|
|
268
275
|
}
|
|
269
276
|
|
|
277
|
+
function handleListQuery(outputFormat) {
|
|
278
|
+
const result = listExecutions(getExecutionStore(), outputFormat);
|
|
279
|
+
if (result.success) {
|
|
280
|
+
console.log(result.output);
|
|
281
|
+
} else {
|
|
282
|
+
console.error(`Error: ${result.error}`);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
270
287
|
/**
|
|
271
288
|
* Handle --cleanup flag
|
|
272
289
|
* Cleans up stale "executing" records (processes that crashed or were killed)
|
|
@@ -314,72 +331,6 @@ function handleCleanup(dryRun) {
|
|
|
314
331
|
}
|
|
315
332
|
}
|
|
316
333
|
|
|
317
|
-
/** Print usage information */
|
|
318
|
-
function printUsage() {
|
|
319
|
-
console.log(`Usage: $ [options] [--] <command> | $ --status <uuid> [--output-format <fmt>]
|
|
320
|
-
|
|
321
|
-
Options:
|
|
322
|
-
--isolated, -i <env> Run in isolated environment (screen, tmux, docker, ssh)
|
|
323
|
-
--attached, -a Run in attached mode (foreground)
|
|
324
|
-
--detached, -d Run in detached mode (background)
|
|
325
|
-
--session, -s <name> Session name for isolation
|
|
326
|
-
--session-id <uuid> Session UUID for tracking (auto-generated if not provided)
|
|
327
|
-
--session-name <uuid> Alias for --session-id
|
|
328
|
-
--image <image> Docker image (required for docker isolation)
|
|
329
|
-
--endpoint <endpoint> SSH endpoint (required for ssh isolation, e.g., user@host)
|
|
330
|
-
--isolated-user, -u [name] Create isolated user with same permissions
|
|
331
|
-
--keep-user Keep isolated user after command completes
|
|
332
|
-
--keep-alive, -k Keep isolation environment alive after command exits
|
|
333
|
-
--auto-remove-docker-container Auto-remove docker container after exit
|
|
334
|
-
--shell <shell> Shell to use in isolation environments: auto, bash, zsh, sh (default: auto)
|
|
335
|
-
--use-command-stream Use command-stream library for execution (experimental)
|
|
336
|
-
--status <id> Show status of execution by UUID or session name (--output-format: links-notation|json|text)
|
|
337
|
-
--cleanup Clean up stale "executing" records (crashed/killed processes)
|
|
338
|
-
--cleanup-dry-run Show stale records that would be cleaned up (without cleaning)
|
|
339
|
-
--version, -v Show version information
|
|
340
|
-
|
|
341
|
-
Examples:
|
|
342
|
-
$ echo "Hello World"
|
|
343
|
-
$ bun test
|
|
344
|
-
$ --isolated tmux -- bun start
|
|
345
|
-
$ -i screen -d bun start
|
|
346
|
-
$ --isolated docker --image oven/bun:latest -- bun install
|
|
347
|
-
$ --isolated ssh --endpoint user@remote.server -- ls -la
|
|
348
|
-
$ --isolated-user -- npm test # Create isolated user
|
|
349
|
-
$ -u myuser -- npm start # Custom username
|
|
350
|
-
$ -i screen --isolated-user -- npm test # Combine with process isolation
|
|
351
|
-
$ --isolated-user --keep-user -- npm start
|
|
352
|
-
$ --use-command-stream echo "Hello" # Use command-stream library`);
|
|
353
|
-
console.log('');
|
|
354
|
-
console.log('Piping with $:');
|
|
355
|
-
console.log(' echo "hi" | $ agent # Preferred - pipe TO $ command');
|
|
356
|
-
console.log(
|
|
357
|
-
' $ \'echo "hi" | agent\' # Alternative - quote entire pipeline'
|
|
358
|
-
);
|
|
359
|
-
console.log('');
|
|
360
|
-
console.log('Quoting for special characters:');
|
|
361
|
-
console.log(" $ 'npm test && npm build' # Wrap for logical operators");
|
|
362
|
-
console.log(" $ 'cat file > output.txt' # Wrap for redirections");
|
|
363
|
-
console.log('');
|
|
364
|
-
console.log('Features:');
|
|
365
|
-
console.log(' - Logs all output to temporary directory');
|
|
366
|
-
console.log(' - Displays timestamps and exit codes');
|
|
367
|
-
console.log(
|
|
368
|
-
' - Auto-reports failures for NPM packages (when gh is available)'
|
|
369
|
-
);
|
|
370
|
-
console.log(' - Natural language command aliases (via substitutions.lino)');
|
|
371
|
-
console.log(' - Process isolation via screen, tmux, or docker');
|
|
372
|
-
console.log('');
|
|
373
|
-
console.log('Alias examples:');
|
|
374
|
-
console.log(' $ install lodash npm package -> npm install lodash');
|
|
375
|
-
console.log(
|
|
376
|
-
' $ install 4.17.21 version of lodash npm package -> npm install lodash@4.17.21'
|
|
377
|
-
);
|
|
378
|
-
console.log(
|
|
379
|
-
' $ clone https://github.com/user/repo repository -> git clone https://github.com/user/repo'
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
334
|
/**
|
|
384
335
|
* Run command in isolation mode
|
|
385
336
|
* @param {object} options - Wrapper options
|
package/src/lib/args-parser.js
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* --use-command-stream Use command-stream library for command execution (experimental)
|
|
21
21
|
* --verbose Enable verbose/debug output (sets START_VERBOSE=1)
|
|
22
22
|
* --status <uuid> Show status of a previous command execution by UUID
|
|
23
|
-
* --
|
|
23
|
+
* --list List all tracked command executions
|
|
24
|
+
* --output-format <format> Output format for status/list (links-notation, json, text)
|
|
24
25
|
* --cleanup Clean up stale "executing" records (processes that crashed or were killed)
|
|
25
26
|
* --cleanup-dry-run Show stale records that would be cleaned up (without actually cleaning)
|
|
26
27
|
*/
|
|
@@ -169,7 +170,8 @@ function parseArgs(args) {
|
|
|
169
170
|
shell: 'auto', // Shell to use in isolation environments: auto, bash, zsh, sh
|
|
170
171
|
useCommandStream: false, // Use command-stream library for command execution
|
|
171
172
|
status: null, // UUID to show status for
|
|
172
|
-
|
|
173
|
+
list: false, // List all tracked execution records
|
|
174
|
+
outputFormat: null, // Output format for status/list (links-notation, json, text)
|
|
173
175
|
cleanup: false, // Clean up stale "executing" records
|
|
174
176
|
cleanupDryRun: false, // Show what would be cleaned without actually cleaning
|
|
175
177
|
};
|
|
@@ -434,6 +436,12 @@ function parseOption(args, index, options) {
|
|
|
434
436
|
return 1;
|
|
435
437
|
}
|
|
436
438
|
|
|
439
|
+
// --list
|
|
440
|
+
if (arg === '--list') {
|
|
441
|
+
options.list = true;
|
|
442
|
+
return 1;
|
|
443
|
+
}
|
|
444
|
+
|
|
437
445
|
// --output-format <format>
|
|
438
446
|
if (arg === '--output-format') {
|
|
439
447
|
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
@@ -656,9 +664,16 @@ function validateOptions(options) {
|
|
|
656
664
|
}
|
|
657
665
|
}
|
|
658
666
|
|
|
659
|
-
//
|
|
660
|
-
if (options.
|
|
661
|
-
throw new Error('--
|
|
667
|
+
// Single-record and list query modes are mutually exclusive
|
|
668
|
+
if (options.status && options.list) {
|
|
669
|
+
throw new Error('Cannot use both --status and --list at the same time');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Output format is only valid with query modes
|
|
673
|
+
if (options.outputFormat && !options.status && !options.list) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
'--output-format option is only valid with --status or --list'
|
|
676
|
+
);
|
|
662
677
|
}
|
|
663
678
|
|
|
664
679
|
// Validate shell option
|
|
@@ -273,6 +273,86 @@ function formatRecord(record, format) {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
function sortRecordsByStartTimeDesc(records) {
|
|
277
|
+
return [...records].sort(
|
|
278
|
+
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function indentBlock(block, spaces) {
|
|
283
|
+
const prefix = ' '.repeat(spaces);
|
|
284
|
+
return block
|
|
285
|
+
.split('\n')
|
|
286
|
+
.map((line) => `${prefix}${line}`)
|
|
287
|
+
.join('\n');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function prepareRecordForList(record) {
|
|
291
|
+
return attachCurrentTime(enrichDetachedStatus(record));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Format execution records as a Links Notation list.
|
|
296
|
+
* @param {Object[]} records - Execution records
|
|
297
|
+
* @returns {string} Links Notation formatted list
|
|
298
|
+
*/
|
|
299
|
+
function formatRecordListAsLinksNotation(records) {
|
|
300
|
+
const lines = ['executions', ` count ${records.length}`];
|
|
301
|
+
|
|
302
|
+
if (records.length === 0) {
|
|
303
|
+
lines.push(' records ()');
|
|
304
|
+
return lines.join('\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
lines.push(' records');
|
|
308
|
+
for (const record of records) {
|
|
309
|
+
lines.push(indentBlock(formatRecordAsLinksNotation(record), 4));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return lines.join('\n');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Format execution records as human-readable text.
|
|
317
|
+
* @param {Object[]} records - Execution records
|
|
318
|
+
* @returns {string} Human-readable list
|
|
319
|
+
*/
|
|
320
|
+
function formatRecordListAsText(records) {
|
|
321
|
+
const lines = ['Executions', `${'='.repeat(50)}`, `Count: ${records.length}`];
|
|
322
|
+
|
|
323
|
+
for (const record of records) {
|
|
324
|
+
lines.push('', formatRecordAsText(record));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return lines.join('\n');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Format execution records based on format type.
|
|
332
|
+
* @param {Object[]} records - Execution records
|
|
333
|
+
* @param {string} format - Output format (links-notation, json, text)
|
|
334
|
+
* @returns {string} Formatted output string
|
|
335
|
+
*/
|
|
336
|
+
function formatRecordList(records, format) {
|
|
337
|
+
switch (format) {
|
|
338
|
+
case 'links-notation':
|
|
339
|
+
return formatRecordListAsLinksNotation(records);
|
|
340
|
+
case 'json':
|
|
341
|
+
return JSON.stringify(
|
|
342
|
+
{
|
|
343
|
+
count: records.length,
|
|
344
|
+
executions: records.map((record) => record.toObject()),
|
|
345
|
+
},
|
|
346
|
+
null,
|
|
347
|
+
2
|
|
348
|
+
);
|
|
349
|
+
case 'text':
|
|
350
|
+
return formatRecordListAsText(records);
|
|
351
|
+
default:
|
|
352
|
+
throw new Error(`Unknown output format: ${format}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
276
356
|
/**
|
|
277
357
|
* Handle status query and output the result
|
|
278
358
|
* @param {Object} store - ExecutionStore instance
|
|
@@ -305,11 +385,39 @@ function queryStatus(store, identifier, outputFormat) {
|
|
|
305
385
|
}
|
|
306
386
|
}
|
|
307
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Handle execution list query and output the result
|
|
390
|
+
* @param {Object} store - ExecutionStore instance
|
|
391
|
+
* @param {string|null} outputFormat - Output format (links-notation, json, text)
|
|
392
|
+
* @returns {{success: boolean, output?: string, error?: string}}
|
|
393
|
+
*/
|
|
394
|
+
function listExecutions(store, outputFormat) {
|
|
395
|
+
if (!store) {
|
|
396
|
+
return { success: false, error: 'Execution tracking is disabled.' };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const records = sortRecordsByStartTimeDesc(
|
|
401
|
+
store.getAll().map(prepareRecordForList)
|
|
402
|
+
);
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
output: formatRecordList(records, outputFormat || 'links-notation'),
|
|
406
|
+
};
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return { success: false, error: err.message };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
308
412
|
module.exports = {
|
|
309
413
|
formatRecordAsLinksNotation,
|
|
310
414
|
formatRecordAsText,
|
|
311
415
|
formatRecord,
|
|
416
|
+
formatRecordListAsLinksNotation,
|
|
417
|
+
formatRecordListAsText,
|
|
418
|
+
formatRecordList,
|
|
312
419
|
queryStatus,
|
|
420
|
+
listExecutions,
|
|
313
421
|
isDetachedSessionAlive,
|
|
314
422
|
enrichDetachedStatus,
|
|
315
423
|
attachCurrentTime,
|
package/src/lib/usage.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Print usage information */
|
|
2
|
+
function printUsage() {
|
|
3
|
+
console.log(`Usage: $ [options] [--] <command> | $ --status <uuid> [--output-format <fmt>] | $ --list [--output-format <fmt>]
|
|
4
|
+
|
|
5
|
+
Options:
|
|
6
|
+
--isolated, -i <env> Run in isolated environment (screen, tmux, docker, ssh)
|
|
7
|
+
--attached, -a Run in attached mode (foreground)
|
|
8
|
+
--detached, -d Run in detached mode (background)
|
|
9
|
+
--session, -s <name> Session name for isolation
|
|
10
|
+
--session-id <uuid> Session UUID for tracking (auto-generated if not provided)
|
|
11
|
+
--session-name <uuid> Alias for --session-id
|
|
12
|
+
--image <image> Docker image (required for docker isolation)
|
|
13
|
+
--endpoint <endpoint> SSH endpoint (required for ssh isolation, e.g., user@host)
|
|
14
|
+
--isolated-user, -u [name] Create isolated user with same permissions
|
|
15
|
+
--keep-user Keep isolated user after command completes
|
|
16
|
+
--keep-alive, -k Keep isolation environment alive after command exits
|
|
17
|
+
--auto-remove-docker-container Auto-remove docker container after exit
|
|
18
|
+
--shell <shell> Shell to use in isolation environments: auto, bash, zsh, sh (default: auto)
|
|
19
|
+
--use-command-stream Use command-stream library for execution (experimental)
|
|
20
|
+
--status <id> Show status of execution by UUID or session name (--output-format: links-notation|json|text)
|
|
21
|
+
--list List all tracked executions (--output-format: links-notation|json|text)
|
|
22
|
+
--cleanup Clean up stale "executing" records (crashed/killed processes)
|
|
23
|
+
--cleanup-dry-run Show stale records that would be cleaned up (without cleaning)
|
|
24
|
+
--version, -v Show version information
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ echo "Hello World"
|
|
28
|
+
$ bun test
|
|
29
|
+
$ --isolated tmux -- bun start
|
|
30
|
+
$ -i screen -d bun start
|
|
31
|
+
$ --isolated docker --image oven/bun:latest -- bun install
|
|
32
|
+
$ --isolated ssh --endpoint user@remote.server -- ls -la
|
|
33
|
+
$ --isolated-user -- npm test # Create isolated user
|
|
34
|
+
$ -u myuser -- npm start # Custom username
|
|
35
|
+
$ -i screen --isolated-user -- npm test # Combine with process isolation
|
|
36
|
+
$ --isolated-user --keep-user -- npm start
|
|
37
|
+
$ --list # List stored execution records
|
|
38
|
+
$ --list --output-format json # List stored records as JSON
|
|
39
|
+
$ --use-command-stream echo "Hello" # Use command-stream library`);
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log('Piping with $:');
|
|
42
|
+
console.log(' echo "hi" | $ agent # Preferred - pipe TO $ command');
|
|
43
|
+
console.log(
|
|
44
|
+
' $ \'echo "hi" | agent\' # Alternative - quote entire pipeline'
|
|
45
|
+
);
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log('Quoting for special characters:');
|
|
48
|
+
console.log(" $ 'npm test && npm build' # Wrap for logical operators");
|
|
49
|
+
console.log(" $ 'cat file > output.txt' # Wrap for redirections");
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Features:');
|
|
52
|
+
console.log(' - Logs all output to temporary directory');
|
|
53
|
+
console.log(' - Displays timestamps and exit codes');
|
|
54
|
+
console.log(
|
|
55
|
+
' - Auto-reports failures for NPM packages (when gh is available)'
|
|
56
|
+
);
|
|
57
|
+
console.log(' - Natural language command aliases (via substitutions.lino)');
|
|
58
|
+
console.log(' - Process isolation via screen, tmux, or docker');
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log('Alias examples:');
|
|
61
|
+
console.log(' $ install lodash npm package -> npm install lodash');
|
|
62
|
+
console.log(
|
|
63
|
+
' $ install 4.17.21 version of lodash npm package -> npm install lodash@4.17.21'
|
|
64
|
+
);
|
|
65
|
+
console.log(
|
|
66
|
+
' $ clone https://github.com/user/repo repository -> git clone https://github.com/user/repo'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { printUsage };
|
package/test/args-parser.test.js
CHANGED
|
@@ -817,13 +817,13 @@ describe('status option', () => {
|
|
|
817
817
|
it('should throw error for missing UUID argument', () => {
|
|
818
818
|
assert.throws(() => {
|
|
819
819
|
parseArgs(['--status']);
|
|
820
|
-
}, /requires a UUID argument/);
|
|
820
|
+
}, /requires a UUID or session name argument/);
|
|
821
821
|
});
|
|
822
822
|
|
|
823
823
|
it('should throw error for --status with -flag as next argument', () => {
|
|
824
824
|
assert.throws(() => {
|
|
825
825
|
parseArgs(['--status', '--output-format']);
|
|
826
|
-
}, /requires a UUID argument/);
|
|
826
|
+
}, /requires a UUID or session name argument/);
|
|
827
827
|
});
|
|
828
828
|
|
|
829
829
|
it('should default status to null', () => {
|
|
@@ -832,6 +832,30 @@ describe('status option', () => {
|
|
|
832
832
|
});
|
|
833
833
|
});
|
|
834
834
|
|
|
835
|
+
describe('list option', () => {
|
|
836
|
+
it('should parse --list flag', () => {
|
|
837
|
+
const result = parseArgs(['--list']);
|
|
838
|
+
assert.strictEqual(result.wrapperOptions.list, true);
|
|
839
|
+
assert.strictEqual(result.command, '');
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('should parse --list with JSON output format', () => {
|
|
843
|
+
const result = parseArgs(['--list', '--output-format', 'json']);
|
|
844
|
+
assert.strictEqual(result.wrapperOptions.list, true);
|
|
845
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, 'json');
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('should normalize output format with --list', () => {
|
|
849
|
+
const result = parseArgs(['--list', '--output-format', 'TEXT']);
|
|
850
|
+
assert.strictEqual(result.wrapperOptions.outputFormat, 'text');
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it('should default list to false', () => {
|
|
854
|
+
const result = parseArgs(['echo', 'hello']);
|
|
855
|
+
assert.strictEqual(result.wrapperOptions.list, false);
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
835
859
|
describe('output-format option', () => {
|
|
836
860
|
it('should parse --output-format with --status', () => {
|
|
837
861
|
const uuid = '12345678-1234-1234-1234-123456789abc';
|
|
@@ -874,7 +898,7 @@ describe('output-format option', () => {
|
|
|
874
898
|
it('should throw error for output-format without status', () => {
|
|
875
899
|
assert.throws(() => {
|
|
876
900
|
parseArgs(['--output-format', 'json', '--', 'npm', 'test']);
|
|
877
|
-
}, /--output-format option is only valid with --status/);
|
|
901
|
+
}, /--output-format option is only valid with --status or --list/);
|
|
878
902
|
});
|
|
879
903
|
|
|
880
904
|
it('should accept all valid output formats', () => {
|
|
@@ -168,6 +168,77 @@ describe('--status query functionality', () => {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
describe('--list query functionality', () => {
|
|
172
|
+
it('should output all tracked executions in links-notation format by default', () => {
|
|
173
|
+
const executingRecord = new ExecutionRecord({
|
|
174
|
+
command: 'sleep 100',
|
|
175
|
+
pid: 99999,
|
|
176
|
+
logPath: '/tmp/executing.log',
|
|
177
|
+
startTime: '2026-04-24T10:00:00.000Z',
|
|
178
|
+
});
|
|
179
|
+
store.save(executingRecord);
|
|
180
|
+
|
|
181
|
+
const result = runCli(['--list']);
|
|
182
|
+
|
|
183
|
+
expect(result.exitCode).toBe(0);
|
|
184
|
+
expect(result.stdout).toMatch(/^executions\n/);
|
|
185
|
+
expect(result.stdout).toContain(' count 2');
|
|
186
|
+
expect(result.stdout).toContain(` ${testRecord.uuid}`);
|
|
187
|
+
expect(result.stdout).toContain(` ${executingRecord.uuid}`);
|
|
188
|
+
expect(result.stdout).toContain(' command "echo hello world"');
|
|
189
|
+
expect(result.stdout).toContain(' command "sleep 100"');
|
|
190
|
+
expect(result.stdout).toContain(' status executed');
|
|
191
|
+
expect(result.stdout).toContain(' status executing');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should output all tracked executions in JSON format', () => {
|
|
195
|
+
const executingRecord = new ExecutionRecord({
|
|
196
|
+
command: 'sleep 100',
|
|
197
|
+
pid: 99999,
|
|
198
|
+
logPath: '/tmp/executing.log',
|
|
199
|
+
});
|
|
200
|
+
store.save(executingRecord);
|
|
201
|
+
|
|
202
|
+
const result = runCli(['--list', '--output-format', 'json']);
|
|
203
|
+
|
|
204
|
+
expect(result.exitCode).toBe(0);
|
|
205
|
+
const parsed = JSON.parse(result.stdout);
|
|
206
|
+
expect(parsed.count).toBe(2);
|
|
207
|
+
expect(parsed.executions.length).toBe(2);
|
|
208
|
+
expect(parsed.executions.map((record) => record.uuid)).toContain(
|
|
209
|
+
testRecord.uuid
|
|
210
|
+
);
|
|
211
|
+
expect(parsed.executions.map((record) => record.uuid)).toContain(
|
|
212
|
+
executingRecord.uuid
|
|
213
|
+
);
|
|
214
|
+
const executing = parsed.executions.find(
|
|
215
|
+
(record) => record.uuid === executingRecord.uuid
|
|
216
|
+
);
|
|
217
|
+
expect(executing.status).toBe('executing');
|
|
218
|
+
expect(executing.currentTime).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should show count zero when no records are stored', () => {
|
|
222
|
+
cleanupTestDir();
|
|
223
|
+
|
|
224
|
+
const result = runCli(['--list']);
|
|
225
|
+
|
|
226
|
+
expect(result.exitCode).toBe(0);
|
|
227
|
+
expect(result.stdout).toMatch(/^executions\n/);
|
|
228
|
+
expect(result.stdout).toContain(' count 0');
|
|
229
|
+
expect(result.stdout).toContain(' records ()');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should show error when tracking is disabled', () => {
|
|
233
|
+
const result = runCli(['--list'], {
|
|
234
|
+
START_DISABLE_TRACKING: '1',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(result.exitCode).toBe(1);
|
|
238
|
+
expect(result.stderr).toContain('tracking is disabled');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
171
242
|
describe('executing status', () => {
|
|
172
243
|
it('should show executing status for ongoing commands', () => {
|
|
173
244
|
// Create an executing (not completed) record
|