start-command 0.37.8 → 0.38.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 +16 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/bin/cli.js +66 -26
- package/src/lib/args-parser-queries.js +90 -9
- package/src/lib/args-parser.js +20 -1
- package/src/lib/container-removal.js +163 -0
- package/src/lib/detached-finalize.js +29 -2
- package/src/lib/docker-cleanup.js +7 -4
- package/src/lib/docker-command-handoff.js +34 -6
- package/src/lib/docker-post-mortem.js +2 -0
- package/src/lib/docker-runtime-args.js +7 -0
- package/src/lib/docker-snapshot-resume.js +222 -0
- package/src/lib/docker-snapshot-safety.js +124 -12
- package/src/lib/execution-attempt.js +21 -9
- package/src/lib/execution-chain.js +182 -0
- package/src/lib/execution-cleanup.js +193 -0
- package/src/lib/execution-control.js +2 -1
- package/src/lib/execution-env.js +120 -0
- package/src/lib/execution-identity.js +180 -0
- package/src/lib/execution-recovery.js +19 -0
- package/src/lib/execution-resume.js +77 -6
- package/src/lib/execution-store.js +63 -22
- package/src/lib/isolation.js +25 -3
- package/src/lib/launch-identity-options.js +81 -0
- package/src/lib/launch-identity.js +411 -0
- package/src/lib/log-uploader.js +15 -1
- package/src/lib/query-commands.js +34 -6
- package/src/lib/screen-isolation.js +11 -2
- package/src/lib/snapshot-resume-options.js +174 -0
- package/src/lib/spawn-helpers.js +33 -16
- package/src/lib/status-formatter.js +64 -35
- package/src/lib/status-links-notation.js +97 -0
- package/src/lib/store-lock.js +3 -0
- package/src/lib/usage.js +15 -1
- package/test/args-parser-attach-resume.js +1 -1
- package/test/args-parser-control.js +2 -2
- package/test/regression-224.js +807 -0
- package/test/regression-225.js +415 -0
- package/test/regression-226.js +81 -0
- package/test/regression-229.js +267 -0
- package/test/regression-230.js +234 -0
- package/test/regression-231.js +298 -0
- package/test/shared-golden.js +35 -0
|
@@ -379,6 +379,23 @@ function recoverKilledExecution(params = {}) {
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
if (opts.commandHandoff && record.uuid) {
|
|
383
|
+
// `docker start` keeps the creation env; refresh START_ATTEMPT (#230).
|
|
384
|
+
try {
|
|
385
|
+
require('./docker-command-handoff').writeExecutionEnvHandoff(
|
|
386
|
+
containerName,
|
|
387
|
+
require('./execution-env').buildExecutionEnv({
|
|
388
|
+
executionId: record.uuid,
|
|
389
|
+
sessionName: containerName,
|
|
390
|
+
attempt: require('./execution-attempt').nextAttemptNumber(record),
|
|
391
|
+
}),
|
|
392
|
+
runner
|
|
393
|
+
);
|
|
394
|
+
} catch {
|
|
395
|
+
// Non-fatal: the command still runs, with the previous START_ATTEMPT.
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
382
399
|
const since = now().toISOString();
|
|
383
400
|
const {
|
|
384
401
|
appendLifecycle,
|
|
@@ -458,6 +475,8 @@ function recoverKilledExecution(params = {}) {
|
|
|
458
475
|
record.observedAt = undefined;
|
|
459
476
|
record.staleDetectedAt = undefined;
|
|
460
477
|
record.containerStartedAt = undefined;
|
|
478
|
+
record.containerState = undefined;
|
|
479
|
+
record.containerRemovedObservedAt = undefined;
|
|
461
480
|
const previousData = globalThis.structuredClone(store.get(executionId));
|
|
462
481
|
const previous = record.constructor.fromObject
|
|
463
482
|
? record.constructor.fromObject(previousData)
|
|
@@ -34,6 +34,7 @@ const {
|
|
|
34
34
|
appendLifecycle,
|
|
35
35
|
archiveAttempt,
|
|
36
36
|
createAttempt,
|
|
37
|
+
nextAttemptNumber,
|
|
37
38
|
patchAttempt,
|
|
38
39
|
} = require('./execution-attempt');
|
|
39
40
|
const { runCommand } = require('./execution-control');
|
|
@@ -46,6 +47,7 @@ const { SessionState, probeSession } = require('./session-probe');
|
|
|
46
47
|
const { getDockerNetworks } = require('./docker-network-lifecycle');
|
|
47
48
|
const {
|
|
48
49
|
writeCommandHandoff,
|
|
50
|
+
writeExecutionEnvHandoff,
|
|
49
51
|
buildCommandHandoffArgs,
|
|
50
52
|
commandHandoffPath,
|
|
51
53
|
} = require('./docker-command-handoff');
|
|
@@ -54,6 +56,7 @@ const {
|
|
|
54
56
|
preflightSnapshot,
|
|
55
57
|
SNAPSHOT_IMAGE_LABEL,
|
|
56
58
|
} = require('./docker-snapshot-safety');
|
|
59
|
+
const snapshotResume = require('./docker-snapshot-resume');
|
|
57
60
|
const {
|
|
58
61
|
ResumeAllAction,
|
|
59
62
|
resumeAllExecutions,
|
|
@@ -109,6 +112,8 @@ function buildLaunchOptions(record) {
|
|
|
109
112
|
rootSession:
|
|
110
113
|
opts.rootSession || opts.sessionNameHistory?.[0] || opts.sessionName,
|
|
111
114
|
resumeCount: (Number(opts.resumeCount) || 0) + 1,
|
|
115
|
+
// START_ATTEMPT of the relaunched command (issue #230).
|
|
116
|
+
attemptNumber: nextAttemptNumber(record),
|
|
112
117
|
image: opts.image || null,
|
|
113
118
|
session: opts.sessionName,
|
|
114
119
|
detached: true,
|
|
@@ -288,6 +293,7 @@ function buildResumePlan(
|
|
|
288
293
|
session: newSessionName,
|
|
289
294
|
containerName: newSessionName,
|
|
290
295
|
sessionId: record.uuid,
|
|
296
|
+
attemptNumber: nextAttemptNumber(record),
|
|
291
297
|
rootSession:
|
|
292
298
|
opts.rootSession || opts.sessionNameHistory?.[0] || sessionName,
|
|
293
299
|
resumeCount: attempt,
|
|
@@ -441,8 +447,10 @@ function activeSessionName(plan) {
|
|
|
441
447
|
* Resume a tracked execution by UUID or session name.
|
|
442
448
|
* @param {?object} store - Execution store
|
|
443
449
|
* @param {string} identifier - UUID or session name
|
|
444
|
-
* @param {object} deps - {command, outputFormat, probe, runner,
|
|
445
|
-
*
|
|
450
|
+
* @param {object} deps - {command, outputFormat, probe, runner, runAsync,
|
|
451
|
+
* startWatcher, runIsolated, removeOriginal, removeOriginalAfter,
|
|
452
|
+
* snapshotOptions, notify}
|
|
453
|
+
* @returns {Promise<{success: boolean, output?: string, error?: string, exitCode?: number}>}
|
|
446
454
|
*/
|
|
447
455
|
async function resumeExecution(store, identifier, deps = {}) {
|
|
448
456
|
if (!store) {
|
|
@@ -605,16 +613,34 @@ async function resumeExecution(store, identifier, deps = {}) {
|
|
|
605
613
|
}
|
|
606
614
|
containerId = launchResult.containerId || null;
|
|
607
615
|
} else {
|
|
616
|
+
const notify = deps.notify || ((line) => process.stderr.write(`${line}\n`));
|
|
617
|
+
const snapshotOptions = { notify, ...deps.snapshotOptions };
|
|
618
|
+
const progress = {
|
|
619
|
+
sessionName: plan.sessionName,
|
|
620
|
+
logPath: record.logPath,
|
|
621
|
+
notify,
|
|
622
|
+
};
|
|
608
623
|
let snapshotLock;
|
|
609
624
|
let snapshotCreated = false;
|
|
610
625
|
let derivedCreated = false;
|
|
611
626
|
try {
|
|
612
627
|
if (plan.mode === ResumeMode.DOCKER_SNAPSHOT) {
|
|
613
|
-
snapshotLock = acquireSnapshotLock(
|
|
628
|
+
snapshotLock = acquireSnapshotLock({
|
|
629
|
+
...snapshotOptions,
|
|
630
|
+
holder: plan.sessionName,
|
|
631
|
+
});
|
|
632
|
+
if (snapshotOptions.prepare) {
|
|
633
|
+
snapshotResume.runSnapshotPrepare(
|
|
634
|
+
plan.sessionName,
|
|
635
|
+
snapshotOptions.prepare,
|
|
636
|
+
runner,
|
|
637
|
+
progress
|
|
638
|
+
);
|
|
639
|
+
}
|
|
614
640
|
const preflight = preflightSnapshot(
|
|
615
641
|
plan.sessionName,
|
|
616
642
|
runner,
|
|
617
|
-
|
|
643
|
+
snapshotOptions
|
|
618
644
|
);
|
|
619
645
|
plan.message += `\n${preflight.message}`;
|
|
620
646
|
if (record.logPath) {
|
|
@@ -636,10 +662,33 @@ async function resumeExecution(store, identifier, deps = {}) {
|
|
|
636
662
|
);
|
|
637
663
|
}
|
|
638
664
|
}
|
|
665
|
+
if (plan.mode === ResumeMode.DOCKER_START && opts.commandHandoff) {
|
|
666
|
+
// `docker start` keeps the creation env; refresh START_ATTEMPT (#230).
|
|
667
|
+
writeExecutionEnvHandoff(
|
|
668
|
+
plan.sessionName,
|
|
669
|
+
require('./execution-env').buildExecutionEnv({
|
|
670
|
+
executionId: record.uuid,
|
|
671
|
+
sessionName: plan.sessionName,
|
|
672
|
+
attempt: attempt.number,
|
|
673
|
+
}),
|
|
674
|
+
runner
|
|
675
|
+
);
|
|
676
|
+
}
|
|
639
677
|
for (const step of plan.steps) {
|
|
640
678
|
let result;
|
|
641
679
|
try {
|
|
642
|
-
result =
|
|
680
|
+
result =
|
|
681
|
+
step.args[0] === 'commit'
|
|
682
|
+
? await snapshotResume.commitWithProgress(step, {
|
|
683
|
+
...progress,
|
|
684
|
+
runAsync:
|
|
685
|
+
deps.runAsync ||
|
|
686
|
+
(deps.runner
|
|
687
|
+
? (command, args) =>
|
|
688
|
+
Promise.resolve().then(() => runner(command, args))
|
|
689
|
+
: snapshotResume.runCommandAsync),
|
|
690
|
+
})
|
|
691
|
+
: runner(step.command, step.args);
|
|
643
692
|
} catch (error) {
|
|
644
693
|
result = { success: false, error: error.message };
|
|
645
694
|
}
|
|
@@ -661,7 +710,11 @@ async function resumeExecution(store, identifier, deps = {}) {
|
|
|
661
710
|
containerId = (result.stdout || '').trim() || containerId;
|
|
662
711
|
}
|
|
663
712
|
}
|
|
664
|
-
if (
|
|
713
|
+
if (
|
|
714
|
+
plan.mode === ResumeMode.DOCKER_SNAPSHOT &&
|
|
715
|
+
deps.removeOriginal &&
|
|
716
|
+
!(deps.removeOriginalAfter > 0)
|
|
717
|
+
) {
|
|
665
718
|
const running = runner(getDockerCommand(), [
|
|
666
719
|
'inspect',
|
|
667
720
|
'-f',
|
|
@@ -704,6 +757,7 @@ async function resumeExecution(store, identifier, deps = {}) {
|
|
|
704
757
|
return {
|
|
705
758
|
success: false,
|
|
706
759
|
error: `Failed to resume ${plan.backend} session "${plan.sessionName}": ${error.message}`,
|
|
760
|
+
...(error.exitCode ? { exitCode: error.exitCode } : {}),
|
|
707
761
|
};
|
|
708
762
|
} finally {
|
|
709
763
|
snapshotLock?.release();
|
|
@@ -793,6 +847,23 @@ async function resumeExecution(store, identifier, deps = {}) {
|
|
|
793
847
|
};
|
|
794
848
|
}
|
|
795
849
|
|
|
850
|
+
if (
|
|
851
|
+
plan.mode === ResumeMode.DOCKER_SNAPSHOT &&
|
|
852
|
+
deps.removeOriginal &&
|
|
853
|
+
deps.removeOriginalAfter > 0
|
|
854
|
+
) {
|
|
855
|
+
plan.message += `\n${await snapshotResume.removeOriginalAfter(
|
|
856
|
+
plan,
|
|
857
|
+
deps.removeOriginalAfter,
|
|
858
|
+
runner,
|
|
859
|
+
{
|
|
860
|
+
logPath: record.logPath,
|
|
861
|
+
notify: deps.notify || ((line) => process.stderr.write(`${line}\n`)),
|
|
862
|
+
sleep: deps.sleep,
|
|
863
|
+
}
|
|
864
|
+
)}`;
|
|
865
|
+
}
|
|
866
|
+
|
|
796
867
|
return {
|
|
797
868
|
success: true,
|
|
798
869
|
output: formatResumeResult(
|
|
@@ -77,6 +77,8 @@ class ExecutionRecord {
|
|
|
77
77
|
this.endTime = options.endTime || null;
|
|
78
78
|
this.attempt = options.attempt;
|
|
79
79
|
this.attemptHistory = options.attemptHistory;
|
|
80
|
+
// UUID of the execution this one continues (`--continues`, issue #231).
|
|
81
|
+
this.continues = options.continues || undefined;
|
|
80
82
|
// Provenance of `endTime` (issue #170.2). `--status` used to fabricate a
|
|
81
83
|
// finish time with `new Date()` whenever the record had none, which made an
|
|
82
84
|
// observation indistinguishable from a real finish time. One of
|
|
@@ -101,6 +103,14 @@ class ExecutionRecord {
|
|
|
101
103
|
options.containerStartedAt !== undefined
|
|
102
104
|
? options.containerStartedAt
|
|
103
105
|
: undefined;
|
|
106
|
+
// `removed` once the container is known to be gone (issue #225), with
|
|
107
|
+
// when that was observed; `options.containerId` is kept as history.
|
|
108
|
+
this.containerState =
|
|
109
|
+
options.containerState !== undefined ? options.containerState : undefined;
|
|
110
|
+
this.containerRemovedObservedAt =
|
|
111
|
+
options.containerRemovedObservedAt !== undefined
|
|
112
|
+
? options.containerRemovedObservedAt
|
|
113
|
+
: undefined;
|
|
104
114
|
this.oomKilled =
|
|
105
115
|
options.oomKilled !== undefined ? options.oomKilled : undefined;
|
|
106
116
|
// Query-time hint explaining an opaque exit code (issue #162). Derived from
|
|
@@ -164,6 +174,9 @@ class ExecutionRecord {
|
|
|
164
174
|
if (this.attemptHistory) {
|
|
165
175
|
obj.attemptHistory = this.attemptHistory;
|
|
166
176
|
}
|
|
177
|
+
if (this.continues) {
|
|
178
|
+
obj.continues = this.continues;
|
|
179
|
+
}
|
|
167
180
|
if (this.endTimeSource !== undefined && this.endTimeSource !== null) {
|
|
168
181
|
obj.endTimeSource = this.endTimeSource;
|
|
169
182
|
}
|
|
@@ -179,6 +192,15 @@ class ExecutionRecord {
|
|
|
179
192
|
) {
|
|
180
193
|
obj.containerStartedAt = this.containerStartedAt;
|
|
181
194
|
}
|
|
195
|
+
if (this.containerState !== undefined && this.containerState !== null) {
|
|
196
|
+
obj.containerState = this.containerState;
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
this.containerRemovedObservedAt !== undefined &&
|
|
200
|
+
this.containerRemovedObservedAt !== null
|
|
201
|
+
) {
|
|
202
|
+
obj.containerRemovedObservedAt = this.containerRemovedObservedAt;
|
|
203
|
+
}
|
|
182
204
|
if (this.oomKilled !== undefined && this.oomKilled !== null) {
|
|
183
205
|
obj.oomKilled = this.oomKilled;
|
|
184
206
|
}
|
|
@@ -489,36 +511,55 @@ class ExecutionStore {
|
|
|
489
511
|
}
|
|
490
512
|
}
|
|
491
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Record that a finished execution's container no longer exists
|
|
516
|
+
* (issue #225). Applied under the database lock to the stored record, and
|
|
517
|
+
* only while it is still finished, so a concurrent resume is never undone.
|
|
518
|
+
* @param {string} uuid - Execution UUID
|
|
519
|
+
* @param {string} observedAt - When the removal was observed
|
|
520
|
+
* @returns {ExecutionRecord|null} The updated record, or null
|
|
521
|
+
*/
|
|
522
|
+
markContainerRemoved(uuid, observedAt) {
|
|
523
|
+
const lock = new LockManager(this.lockFilePath);
|
|
524
|
+
if (!lock.acquire()) {
|
|
525
|
+
throw new Error('Failed to acquire lock for container state');
|
|
526
|
+
}
|
|
527
|
+
try {
|
|
528
|
+
const records = this.readLinoRecords();
|
|
529
|
+
const current = records.find((record) => record.uuid === uuid);
|
|
530
|
+
if (
|
|
531
|
+
!current ||
|
|
532
|
+
current.status !== ExecutionStatus.EXECUTED ||
|
|
533
|
+
current.containerState === 'removed'
|
|
534
|
+
) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
current.containerState = 'removed';
|
|
538
|
+
current.containerRemovedObservedAt = observedAt;
|
|
539
|
+
this.writeLinoRecords(records);
|
|
540
|
+
if (this.useLinks) {
|
|
541
|
+
this.writeLinksRecord(current);
|
|
542
|
+
}
|
|
543
|
+
return current;
|
|
544
|
+
} finally {
|
|
545
|
+
lock.release();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
492
549
|
/**
|
|
493
550
|
* Get an execution record by UUID or session name
|
|
494
551
|
* First tries exact UUID match, then the current session name, then any
|
|
495
|
-
* session name the execution used before it was resumed
|
|
552
|
+
* session name the execution used before it was resumed, then the display
|
|
553
|
+
* name and aliases
|
|
496
554
|
* @param {string} identifier - UUID or session name
|
|
497
555
|
* @returns {ExecutionRecord|null}
|
|
498
556
|
*/
|
|
499
557
|
get(identifier) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return byUuid;
|
|
505
|
-
}
|
|
506
|
-
// Fall back to session name lookup (stored in options.sessionName)
|
|
507
|
-
const bySessionName = records.find(
|
|
508
|
-
(r) => r.options && r.options.sessionName === identifier
|
|
509
|
-
);
|
|
510
|
-
if (bySessionName) {
|
|
511
|
-
return bySessionName;
|
|
512
|
-
}
|
|
513
|
-
// Finally, honor session names this record used before it was resumed, so
|
|
514
|
-
// one logical session stays addressable across restarts (issue #162).
|
|
515
|
-
const byPreviousSessionName = records.find(
|
|
516
|
-
(r) =>
|
|
517
|
-
r.options &&
|
|
518
|
-
Array.isArray(r.options.sessionNameHistory) &&
|
|
519
|
-
r.options.sessionNameHistory.includes(identifier)
|
|
558
|
+
// Names, display names and aliases resolve too (issues #162, #231).
|
|
559
|
+
return require('./execution-identity').findRecord(
|
|
560
|
+
this.readLinoRecords(),
|
|
561
|
+
identifier
|
|
520
562
|
);
|
|
521
|
-
return byPreviousSessionName || null;
|
|
522
563
|
}
|
|
523
564
|
|
|
524
565
|
/**
|
package/src/lib/isolation.js
CHANGED
|
@@ -201,7 +201,8 @@ function runScreenWithLogCapture(
|
|
|
201
201
|
sessionName,
|
|
202
202
|
shellInfo,
|
|
203
203
|
user = null,
|
|
204
|
-
logPath = null
|
|
204
|
+
logPath = null,
|
|
205
|
+
env = null
|
|
205
206
|
) {
|
|
206
207
|
return _runScreenWithLogCapture(
|
|
207
208
|
command,
|
|
@@ -210,7 +211,8 @@ function runScreenWithLogCapture(
|
|
|
210
211
|
user,
|
|
211
212
|
wrapCommandWithUser,
|
|
212
213
|
isInteractiveShellCommand,
|
|
213
|
-
logPath
|
|
214
|
+
logPath,
|
|
215
|
+
env
|
|
214
216
|
);
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -241,6 +243,7 @@ function runInScreen(command, options = {}) {
|
|
|
241
243
|
keepAlive: options.keepAlive,
|
|
242
244
|
logPath: options.logPath,
|
|
243
245
|
wrapCommandWithUser,
|
|
246
|
+
env: options.executionEnv,
|
|
244
247
|
})
|
|
245
248
|
);
|
|
246
249
|
} else {
|
|
@@ -249,7 +252,8 @@ function runInScreen(command, options = {}) {
|
|
|
249
252
|
sessionName,
|
|
250
253
|
shellInfo,
|
|
251
254
|
options.user,
|
|
252
|
-
options.logPath
|
|
255
|
+
options.logPath,
|
|
256
|
+
options.executionEnv
|
|
253
257
|
);
|
|
254
258
|
}
|
|
255
259
|
} catch (err) {
|
|
@@ -813,8 +817,10 @@ function runInDocker(command, options = {}) {
|
|
|
813
817
|
!containerExistedBeforeLaunch &&
|
|
814
818
|
readDockerContainerStatus(containerName) === 'created';
|
|
815
819
|
|
|
820
|
+
let containerRemoved = false;
|
|
816
821
|
if (launchFailed) {
|
|
817
822
|
if (removeDockerContainer(containerName, options.logPath)) {
|
|
823
|
+
containerRemoved = true;
|
|
818
824
|
message += `\nContainer removed after launch failure.`;
|
|
819
825
|
} else {
|
|
820
826
|
message += `\nWarning: failed to remove container after launch failure.`;
|
|
@@ -823,6 +829,7 @@ function runInDocker(command, options = {}) {
|
|
|
823
829
|
shouldCleanupDockerContainer(cleanupPolicy, exitCode, oomKilled)
|
|
824
830
|
) {
|
|
825
831
|
if (removeDockerContainer(containerName, options.logPath)) {
|
|
832
|
+
containerRemoved = true;
|
|
826
833
|
message += `\nContainer removed after completion.${postMortem(true)}`;
|
|
827
834
|
} else {
|
|
828
835
|
message += `\nWarning: failed to remove container automatically.`;
|
|
@@ -848,6 +855,7 @@ function runInDocker(command, options = {}) {
|
|
|
848
855
|
diagnostics: diagnosticFacts,
|
|
849
856
|
message,
|
|
850
857
|
exitCode,
|
|
858
|
+
containerRemoved,
|
|
851
859
|
});
|
|
852
860
|
});
|
|
853
861
|
|
|
@@ -905,6 +913,20 @@ function runIsolated(backend, command, options = {}) {
|
|
|
905
913
|
session: options.sessionStack ? options.sessionStack[0] : options.session,
|
|
906
914
|
logPath: options.logPath,
|
|
907
915
|
};
|
|
916
|
+
// START_* identity (issue #230): docker passes it with `-e`; a bare screen
|
|
917
|
+
// shell inherits the spawn env; everything else gets an `export` prefix.
|
|
918
|
+
const identity = require('./execution-env');
|
|
919
|
+
currentOptions.executionEnv =
|
|
920
|
+
identity.executionEnvFromOptions(currentOptions);
|
|
921
|
+
if (
|
|
922
|
+
backend !== 'docker' &&
|
|
923
|
+
!(backend === 'screen' && isInteractiveShellCommand(effectiveCommand))
|
|
924
|
+
) {
|
|
925
|
+
effectiveCommand = identity.prefixWithExecutionEnv(
|
|
926
|
+
effectiveCommand,
|
|
927
|
+
currentOptions.executionEnv
|
|
928
|
+
);
|
|
929
|
+
}
|
|
908
930
|
|
|
909
931
|
switch (backend) {
|
|
910
932
|
case 'screen':
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch options about the identity of a new execution:
|
|
3
|
+
* --append-attempt run as the next attempt of the tracked execution named
|
|
4
|
+
* by --session-id instead of being refused (issue #229)
|
|
5
|
+
* --continues <id> record that this execution continues an earlier one,
|
|
6
|
+
* which `--status` and `--upload-log` follow (issue #231)
|
|
7
|
+
*
|
|
8
|
+
* Both only apply when a command is launched, not to the query modes.
|
|
9
|
+
*/
|
|
10
|
+
const { activeQueryModes } = require('./args-parser-queries');
|
|
11
|
+
|
|
12
|
+
function createLaunchIdentityOptionDefaults() {
|
|
13
|
+
return {
|
|
14
|
+
appendAttempt: false,
|
|
15
|
+
continues: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse one launch identity option.
|
|
21
|
+
* @param {string[]} args - Arguments array
|
|
22
|
+
* @param {number} index - Current index
|
|
23
|
+
* @param {object} options - Options object to populate
|
|
24
|
+
* @returns {number} Number of arguments consumed (0 if not recognized)
|
|
25
|
+
*/
|
|
26
|
+
function parse(args, index, options) {
|
|
27
|
+
const arg = args[index];
|
|
28
|
+
if (arg === '--append-attempt') {
|
|
29
|
+
options.appendAttempt = true;
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (arg === '--continues') {
|
|
33
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
34
|
+
options.continues = args[index + 1];
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Option --continues requires a UUID or session name argument'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (arg.startsWith('--continues=')) {
|
|
42
|
+
const value = arg.slice('--continues='.length);
|
|
43
|
+
if (!value) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Option --continues requires a UUID or session name argument'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
options.continues = value;
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate the launch identity options.
|
|
56
|
+
* @param {object} options - Parsed wrapper options
|
|
57
|
+
* @throws {Error} When the combination is not supported
|
|
58
|
+
*/
|
|
59
|
+
function validate(options) {
|
|
60
|
+
const used = [
|
|
61
|
+
...(options.appendAttempt ? ['--append-attempt'] : []),
|
|
62
|
+
...(options.continues !== null && options.continues !== undefined
|
|
63
|
+
? ['--continues']
|
|
64
|
+
: []),
|
|
65
|
+
];
|
|
66
|
+
if (used.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (activeQueryModes(options).length > 0) {
|
|
70
|
+
throw new Error(`${used[0]} option is only valid when launching a command`);
|
|
71
|
+
}
|
|
72
|
+
if (options.appendAttempt && !options.sessionId) {
|
|
73
|
+
throw new Error('--append-attempt option requires --session-id <uuid>');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
createLaunchIdentityOptionDefaults,
|
|
79
|
+
parse,
|
|
80
|
+
validate,
|
|
81
|
+
};
|