@lumpcode/core 0.0.14 → 0.0.16
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/README.md +7 -1
- package/dist/helpers/execBinary/main.d.ts +19 -5
- package/dist/helpers/execBinary/main.d.ts.map +1 -1
- package/dist/helpers/executeStepsForContextList/main.d.ts +10 -3
- package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
- package/dist/index.cjs +410 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +409 -161
- package/dist/index.js.map +1 -1
- package/dist/testing/processTreeChild.d.cts +2 -0
- package/dist/testing/processTreeChild.d.cts.map +1 -0
- package/dist/testing/processTreeTestHelpers.d.ts +12 -0
- package/dist/testing/processTreeTestHelpers.d.ts.map +1 -0
- package/dist/testing/sigtermIgnorantTreeChild.d.cts +2 -0
- package/dist/testing/sigtermIgnorantTreeChild.d.cts.map +1 -0
- package/dist/types/ExecuteStepsFailureData.d.ts +6 -0
- package/dist/types/ExecuteStepsFailureData.d.ts.map +1 -0
- package/dist/types/ExecuteStepsFailureReason.d.ts +2 -0
- package/dist/types/ExecuteStepsFailureReason.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/usages/runLump/main.d.ts +4 -4
- package/dist/usages/runLump/main.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/isProcessAlive/index.d.ts +2 -1
- package/dist/utils/isProcessAlive/index.d.ts.map +1 -1
- package/dist/utils/killProcessTree/index.d.ts +2 -0
- package/dist/utils/killProcessTree/index.d.ts.map +1 -0
- package/dist/utils/killProcessTree/main.d.ts +11 -0
- package/dist/utils/killProcessTree/main.d.ts.map +1 -0
- package/dist/utils/nodeErrnoCode/index.d.ts +1 -1
- package/dist/utils/nodeErrnoCode/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { exec, spawn } from 'node:child_process';
|
|
1
|
+
import { execFile, exec, spawn } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import * as path from 'node:path';
|
|
@@ -3403,6 +3403,152 @@ function parseGitLogHashSubjectLines(stdout) {
|
|
|
3403
3403
|
});
|
|
3404
3404
|
}
|
|
3405
3405
|
|
|
3406
|
+
/** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
|
|
3407
|
+
function nodeErrnoCode(error) {
|
|
3408
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
3409
|
+
const code = error.code;
|
|
3410
|
+
return typeof code === 'string' ? code : undefined;
|
|
3411
|
+
}
|
|
3412
|
+
return undefined;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/** Returns whether a process id is still running (signal 0 probe). */
|
|
3416
|
+
function isProcessAlive(pid, options) {
|
|
3417
|
+
const onProbeError = options?.onProbeError ?? 'throw';
|
|
3418
|
+
try {
|
|
3419
|
+
process.kill(pid, 0);
|
|
3420
|
+
return true;
|
|
3421
|
+
}
|
|
3422
|
+
catch (error) {
|
|
3423
|
+
if (nodeErrnoCode(error) === 'ESRCH')
|
|
3424
|
+
return false;
|
|
3425
|
+
if (onProbeError === 'throw')
|
|
3426
|
+
throw error;
|
|
3427
|
+
return onProbeError === 'alive';
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
const execFileAsync = promisify(execFile);
|
|
3432
|
+
function sleep(ms) {
|
|
3433
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3434
|
+
}
|
|
3435
|
+
async function listUnixProcessTreePids(rootPid) {
|
|
3436
|
+
const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,ppid=']);
|
|
3437
|
+
const childrenByParent = new Map();
|
|
3438
|
+
for (const line of stdout.split('\n')) {
|
|
3439
|
+
const trimmed = line.trim();
|
|
3440
|
+
if (!trimmed)
|
|
3441
|
+
continue;
|
|
3442
|
+
const parts = trimmed.split(/\s+/);
|
|
3443
|
+
if (parts.length < 2)
|
|
3444
|
+
continue;
|
|
3445
|
+
const pid = Number.parseInt(parts[0] ?? '', 10);
|
|
3446
|
+
const ppid = Number.parseInt(parts[1] ?? '', 10);
|
|
3447
|
+
if (Number.isNaN(pid) || Number.isNaN(ppid))
|
|
3448
|
+
continue;
|
|
3449
|
+
const siblings = childrenByParent.get(ppid) ?? [];
|
|
3450
|
+
siblings.push(pid);
|
|
3451
|
+
childrenByParent.set(ppid, siblings);
|
|
3452
|
+
}
|
|
3453
|
+
const ordered = [];
|
|
3454
|
+
const queue = [rootPid];
|
|
3455
|
+
while (queue.length > 0) {
|
|
3456
|
+
const pid = queue.shift();
|
|
3457
|
+
if (pid === undefined)
|
|
3458
|
+
break;
|
|
3459
|
+
ordered.push(pid);
|
|
3460
|
+
const children = childrenByParent.get(pid) ?? [];
|
|
3461
|
+
for (const childPid of children) {
|
|
3462
|
+
queue.push(childPid);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
return ordered;
|
|
3466
|
+
}
|
|
3467
|
+
function killPid(pid, signal) {
|
|
3468
|
+
try {
|
|
3469
|
+
process.kill(pid, signal);
|
|
3470
|
+
}
|
|
3471
|
+
catch (error) {
|
|
3472
|
+
const code = nodeErrnoCode(error);
|
|
3473
|
+
if (code !== 'ESRCH') {
|
|
3474
|
+
throw error;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
async function killUnixProcessTreeImmediate(rootPid) {
|
|
3479
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3480
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3481
|
+
killPid(pids[index], 'SIGKILL');
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
async function killUnixProcessTreeWithGrace(rootPid, graceMs) {
|
|
3485
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3486
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3487
|
+
killPid(pids[index], 'SIGTERM');
|
|
3488
|
+
}
|
|
3489
|
+
const deadline = Date.now() + graceMs;
|
|
3490
|
+
while (Date.now() < deadline) {
|
|
3491
|
+
const anyAlive = pids.some((pid) => isProcessAlive(pid, { onProbeError: 'dead' }));
|
|
3492
|
+
if (!anyAlive) {
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
await sleep(25);
|
|
3496
|
+
}
|
|
3497
|
+
const remaining = await listUnixProcessTreePids(rootPid);
|
|
3498
|
+
for (let index = remaining.length - 1; index >= 0; index -= 1) {
|
|
3499
|
+
killPid(remaining[index], 'SIGKILL');
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
async function killWindowsProcessTree(rootPid) {
|
|
3503
|
+
try {
|
|
3504
|
+
await execFileAsync('taskkill', ['/PID', String(rootPid), '/T', '/F'], {
|
|
3505
|
+
windowsHide: true,
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
catch (error) {
|
|
3509
|
+
const stderr = error && typeof error === 'object' && 'stderr' in error
|
|
3510
|
+
? String(error.stderr ?? '')
|
|
3511
|
+
: '';
|
|
3512
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3513
|
+
const combined = `${message}\n${stderr}`;
|
|
3514
|
+
if (/not found|no running instance|ne existe pas|introuvable/i.test(combined)) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
// taskkill can fail to terminate some descendants while the root exits (SEA/agent trees).
|
|
3518
|
+
if (!isProcessAlive(rootPid)) {
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
throw error;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
/**
|
|
3525
|
+
* Kill a process and its descendants.
|
|
3526
|
+
* `graceMs` default 0 → immediate SIGKILL / taskkill /T /F.
|
|
3527
|
+
* When `graceMs > 0` (Unix), SIGTERM first, then SIGKILL after the grace window.
|
|
3528
|
+
*/
|
|
3529
|
+
async function killProcessTree(input) {
|
|
3530
|
+
const { pid, graceMs = 0 } = input;
|
|
3531
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
3532
|
+
return failure(`Invalid pid: ${pid}`);
|
|
3533
|
+
}
|
|
3534
|
+
try {
|
|
3535
|
+
if (process.platform === 'win32') {
|
|
3536
|
+
await killWindowsProcessTree(pid);
|
|
3537
|
+
}
|
|
3538
|
+
else if (graceMs > 0) {
|
|
3539
|
+
await killUnixProcessTreeWithGrace(pid, graceMs);
|
|
3540
|
+
}
|
|
3541
|
+
else {
|
|
3542
|
+
await killUnixProcessTreeImmediate(pid);
|
|
3543
|
+
}
|
|
3544
|
+
return success(undefined);
|
|
3545
|
+
}
|
|
3546
|
+
catch (error) {
|
|
3547
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3548
|
+
return failure(`Could not kill process tree for pid ${pid}: ${message}`);
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3406
3552
|
const execAsyncBase = promisify(exec);
|
|
3407
3553
|
async function execAsync(command, options) {
|
|
3408
3554
|
const { stdout, stderr, hasErrored } = await execAsyncBase(command, { cwd: options?.cwd })
|
|
@@ -3432,38 +3578,86 @@ async function execAsync(command, options) {
|
|
|
3432
3578
|
});
|
|
3433
3579
|
}
|
|
3434
3580
|
|
|
3435
|
-
function execBinary(
|
|
3581
|
+
function execBinary(input) {
|
|
3582
|
+
const { binaryPath, args, timeoutMillis = 1000 * 60 * 10, cwd, env, stdio, signal, killGraceMs = 5000, } = input;
|
|
3436
3583
|
return new Promise((resolve) => {
|
|
3437
3584
|
let settled = false;
|
|
3585
|
+
let canceling = false;
|
|
3586
|
+
let child;
|
|
3587
|
+
let stdout = '';
|
|
3588
|
+
let stderr = '';
|
|
3589
|
+
let timeout;
|
|
3438
3590
|
const finish = (result) => {
|
|
3439
3591
|
if (settled)
|
|
3440
3592
|
return;
|
|
3441
3593
|
settled = true;
|
|
3442
|
-
|
|
3594
|
+
if (timeout !== undefined)
|
|
3595
|
+
clearTimeout(timeout);
|
|
3596
|
+
if (signal) {
|
|
3597
|
+
signal.removeEventListener('abort', onAbort);
|
|
3598
|
+
}
|
|
3443
3599
|
resolve(result);
|
|
3444
3600
|
};
|
|
3445
|
-
const
|
|
3601
|
+
const cancelWith = async (reason, message) => {
|
|
3602
|
+
if (settled || canceling)
|
|
3603
|
+
return;
|
|
3604
|
+
canceling = true;
|
|
3605
|
+
const pid = child?.pid;
|
|
3606
|
+
if (pid != null) {
|
|
3607
|
+
await killProcessTree({ pid, graceMs: killGraceMs });
|
|
3608
|
+
}
|
|
3446
3609
|
finish(failure({
|
|
3447
|
-
message
|
|
3610
|
+
message,
|
|
3448
3611
|
binaryPath,
|
|
3449
3612
|
args,
|
|
3613
|
+
stdout,
|
|
3614
|
+
stderr,
|
|
3615
|
+
reason,
|
|
3450
3616
|
}));
|
|
3451
|
-
}
|
|
3617
|
+
};
|
|
3618
|
+
const onAbort = () => {
|
|
3619
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3620
|
+
};
|
|
3621
|
+
if (signal?.aborted) {
|
|
3622
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
if (signal) {
|
|
3626
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3627
|
+
}
|
|
3452
3628
|
const { executable: resolvedExecutable, args: resolvedArgs } = resolveSpawnExecutable(binaryPath, args);
|
|
3453
|
-
const
|
|
3454
|
-
|
|
3629
|
+
const spawnOptions = {
|
|
3630
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
3631
|
+
...(env !== undefined ? { env } : {}),
|
|
3632
|
+
...(stdio !== undefined ? { stdio } : {}),
|
|
3633
|
+
};
|
|
3634
|
+
child = spawn(resolvedExecutable, resolvedArgs, spawnOptions);
|
|
3635
|
+
// Start the timeout only after the OS has spawned the process so
|
|
3636
|
+
// timeoutMillis measures command runtime, not spawn/setup overhead.
|
|
3637
|
+
child.on('spawn', () => {
|
|
3638
|
+
if (settled || canceling)
|
|
3639
|
+
return;
|
|
3640
|
+
timeout = setTimeout(() => {
|
|
3641
|
+
void cancelWith('timeout', `Process timed out after ${timeoutMillis} milliseconds`);
|
|
3642
|
+
}, timeoutMillis);
|
|
3643
|
+
});
|
|
3455
3644
|
child.stdout?.on('data', (data) => { stdout += data; });
|
|
3456
3645
|
child.stderr?.on('data', (data) => { stderr += data; });
|
|
3457
3646
|
child.on('error', (err) => {
|
|
3647
|
+
if (settled || canceling)
|
|
3648
|
+
return;
|
|
3458
3649
|
finish(failure({
|
|
3459
3650
|
message: err.message,
|
|
3460
3651
|
binaryPath,
|
|
3461
3652
|
args,
|
|
3462
3653
|
stdout,
|
|
3463
3654
|
stderr,
|
|
3655
|
+
reason: 'spawn',
|
|
3464
3656
|
}));
|
|
3465
3657
|
});
|
|
3466
3658
|
child.on('close', (code) => {
|
|
3659
|
+
if (settled || canceling)
|
|
3660
|
+
return;
|
|
3467
3661
|
if (code === 0) {
|
|
3468
3662
|
finish(success({ stdout, stderr }));
|
|
3469
3663
|
return;
|
|
@@ -3475,6 +3669,7 @@ function execBinary(binaryPath, args, timeoutMillis = 1000 * 60 * 10, options) {
|
|
|
3475
3669
|
code: code ?? undefined,
|
|
3476
3670
|
stdout,
|
|
3477
3671
|
stderr,
|
|
3672
|
+
reason: 'exit',
|
|
3478
3673
|
}));
|
|
3479
3674
|
});
|
|
3480
3675
|
});
|
|
@@ -3550,7 +3745,7 @@ async function collectStepsForContext(params) {
|
|
|
3550
3745
|
return collectedSteps;
|
|
3551
3746
|
}
|
|
3552
3747
|
|
|
3553
|
-
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, }) {
|
|
3748
|
+
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
|
|
3554
3749
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
3555
3750
|
const contextNames = contextList.map(context => context.name);
|
|
3556
3751
|
logger.verbose(`contextNames ${JSON.stringify(contextNames)}`);
|
|
@@ -3585,180 +3780,232 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3585
3780
|
const gitStatusCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3586
3781
|
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusCommand.data)}`);
|
|
3587
3782
|
injectedGitAndWorkspaceFnsInput.workspacePath = workspacePath;
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
if (typeof step === 'function') {
|
|
3611
|
-
subSteps =
|
|
3783
|
+
let runFailure;
|
|
3784
|
+
try {
|
|
3785
|
+
for (let i = 0; i < contextList.length; i++) {
|
|
3786
|
+
const context = contextList[i];
|
|
3787
|
+
logger.info(contextList.length > 1
|
|
3788
|
+
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
3789
|
+
: `Running context "${context.name}"`);
|
|
3790
|
+
const setupResult = await setupFn({
|
|
3791
|
+
contextList,
|
|
3792
|
+
lumpVariables,
|
|
3793
|
+
currentContextIndex: i,
|
|
3794
|
+
});
|
|
3795
|
+
const contextRunState = setupResult?.contextRunState || {};
|
|
3796
|
+
let stepWalkFailure;
|
|
3797
|
+
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3798
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3799
|
+
if (stepWalkFailure) {
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
const step = stepsToExec[stepIndex];
|
|
3803
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3804
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3805
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3806
|
+
let subSteps = [];
|
|
3807
|
+
if (typeof step === 'function') {
|
|
3808
|
+
subSteps = await step({
|
|
3809
|
+
context,
|
|
3810
|
+
stepIndex: compositeStepIndex,
|
|
3811
|
+
contextRunState,
|
|
3812
|
+
lumpVariables,
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
else {
|
|
3816
|
+
subSteps = step;
|
|
3817
|
+
}
|
|
3818
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3819
|
+
continue;
|
|
3820
|
+
}
|
|
3821
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3822
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3823
|
+
const prompt = promptFn
|
|
3824
|
+
? await promptFn({
|
|
3612
3825
|
context,
|
|
3613
3826
|
stepIndex: compositeStepIndex,
|
|
3614
3827
|
contextRunState,
|
|
3615
3828
|
lumpVariables,
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
}
|
|
3621
|
-
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3622
|
-
continue;
|
|
3623
|
-
}
|
|
3624
|
-
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3625
|
-
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3626
|
-
const prompt = promptFn
|
|
3627
|
-
? await promptFn({
|
|
3829
|
+
stepVariables,
|
|
3830
|
+
})
|
|
3831
|
+
: '';
|
|
3832
|
+
const command = await commandFn({
|
|
3628
3833
|
context,
|
|
3834
|
+
prompt,
|
|
3629
3835
|
stepIndex: compositeStepIndex,
|
|
3630
3836
|
contextRunState,
|
|
3631
3837
|
lumpVariables,
|
|
3632
3838
|
stepVariables,
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
const command = await commandFn({
|
|
3636
|
-
context,
|
|
3637
|
-
prompt,
|
|
3638
|
-
stepIndex: compositeStepIndex,
|
|
3639
|
-
contextRunState,
|
|
3640
|
-
lumpVariables,
|
|
3641
|
-
stepVariables,
|
|
3642
|
-
projectRoot,
|
|
3643
|
-
workspacePath,
|
|
3644
|
-
});
|
|
3645
|
-
let commandResult = '';
|
|
3646
|
-
let commandSucceeded = true;
|
|
3647
|
-
if (command != null) {
|
|
3648
|
-
const { executable, args, env } = command;
|
|
3649
|
-
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3650
|
-
if (env != null) {
|
|
3651
|
-
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3652
|
-
}
|
|
3653
|
-
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3654
|
-
const commandExec = await execBinary(executable, args, timeoutMillis, {
|
|
3655
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3656
|
-
cwd: workspacePath,
|
|
3657
|
-
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3839
|
+
projectRoot,
|
|
3840
|
+
workspacePath,
|
|
3658
3841
|
});
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3842
|
+
let commandResult = '';
|
|
3843
|
+
let commandSucceeded = true;
|
|
3844
|
+
if (command != null) {
|
|
3845
|
+
const { executable, args, env } = command;
|
|
3846
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3847
|
+
if (env != null) {
|
|
3848
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3849
|
+
}
|
|
3850
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3851
|
+
const commandExec = await execBinary({
|
|
3852
|
+
binaryPath: executable,
|
|
3853
|
+
args,
|
|
3854
|
+
timeoutMillis,
|
|
3855
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3856
|
+
cwd: workspacePath,
|
|
3857
|
+
signal,
|
|
3858
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3859
|
+
});
|
|
3860
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
3861
|
+
if (!commandExec.success) {
|
|
3862
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
3863
|
+
if (aborted || !continueOnError) {
|
|
3864
|
+
stepWalkFailure = {
|
|
3865
|
+
success: false,
|
|
3866
|
+
data: {
|
|
3867
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
3868
|
+
reason: 'stepWalkFailed',
|
|
3869
|
+
},
|
|
3870
|
+
};
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
commandSucceeded = false;
|
|
3874
|
+
commandResult = (commandExec.data.stdout
|
|
3875
|
+
|| commandExec.data.stderr
|
|
3876
|
+
|| commandExec.data.message
|
|
3877
|
+
|| '').toString();
|
|
3878
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3879
|
+
}
|
|
3880
|
+
else {
|
|
3881
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3882
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3883
|
+
}
|
|
3884
|
+
if (commandSucceeded) {
|
|
3885
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3886
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3664
3887
|
}
|
|
3665
|
-
commandSucceeded = false;
|
|
3666
|
-
commandResult = (commandExec.data.stdout
|
|
3667
|
-
|| commandExec.data.stderr
|
|
3668
|
-
|| commandExec.data.message
|
|
3669
|
-
|| '').toString();
|
|
3670
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3671
3888
|
}
|
|
3672
|
-
|
|
3673
|
-
commandResult
|
|
3674
|
-
|
|
3889
|
+
const postCommandExecFnInput = {
|
|
3890
|
+
commandResult,
|
|
3891
|
+
commandSucceeded,
|
|
3892
|
+
context,
|
|
3893
|
+
prompt,
|
|
3894
|
+
stepIndex: compositeStepIndex,
|
|
3895
|
+
contextRunState,
|
|
3896
|
+
lumpVariables,
|
|
3897
|
+
stepVariables,
|
|
3898
|
+
projectRoot,
|
|
3899
|
+
};
|
|
3900
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3901
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3902
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3903
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3904
|
+
const appendResult = await appendHistoryEntry({
|
|
3905
|
+
filePath: keepHistoryFilePath,
|
|
3906
|
+
entry: postCommandExecFnInput,
|
|
3907
|
+
});
|
|
3908
|
+
if (!appendResult.success) {
|
|
3909
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3910
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3911
|
+
}
|
|
3675
3912
|
}
|
|
3676
|
-
if (
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3913
|
+
if (postCommandExecFn) {
|
|
3914
|
+
const returnedSteps = await postCommandExecFn(postCommandExecFnInput);
|
|
3915
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
3916
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3917
|
+
}
|
|
3679
3918
|
}
|
|
3680
3919
|
}
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3693
|
-
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3694
|
-
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3695
|
-
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3696
|
-
const appendResult = await appendHistoryEntry({
|
|
3697
|
-
filePath: keepHistoryFilePath,
|
|
3698
|
-
entry: postCommandExecFnInput,
|
|
3920
|
+
}
|
|
3921
|
+
try {
|
|
3922
|
+
await walkAndExecuteSteps(steps, []);
|
|
3923
|
+
}
|
|
3924
|
+
finally {
|
|
3925
|
+
try {
|
|
3926
|
+
await teardownFn({
|
|
3927
|
+
lumpVariables,
|
|
3928
|
+
contextList,
|
|
3929
|
+
currentContextIndex: i,
|
|
3930
|
+
contextRunState,
|
|
3699
3931
|
});
|
|
3700
|
-
if (!appendResult.success) {
|
|
3701
|
-
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3702
|
-
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3703
|
-
}
|
|
3704
3932
|
}
|
|
3705
|
-
|
|
3706
|
-
const
|
|
3707
|
-
|
|
3708
|
-
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3709
|
-
}
|
|
3933
|
+
catch (error) {
|
|
3934
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3935
|
+
logger.error(`Failed to run teardownFn: ${message}`);
|
|
3710
3936
|
}
|
|
3711
3937
|
}
|
|
3938
|
+
if (stepWalkFailure) {
|
|
3939
|
+
runFailure = stepWalkFailure;
|
|
3940
|
+
break;
|
|
3941
|
+
}
|
|
3942
|
+
const perContextInput = {
|
|
3943
|
+
...injectedGitAndWorkspaceFnsInput,
|
|
3944
|
+
context,
|
|
3945
|
+
};
|
|
3946
|
+
const gitAddCommand = await execAsync(gitAddCommandFn(perContextInput), { cwd: workspacePath });
|
|
3947
|
+
logger.verbose(`gitAddCommand ${JSON.stringify(gitAddCommand)}`);
|
|
3948
|
+
if (!gitAddCommand.success) {
|
|
3949
|
+
runFailure = {
|
|
3950
|
+
success: false,
|
|
3951
|
+
data: {
|
|
3952
|
+
message: `Failed to add the changes for context ${context.name}: ${gitAddCommand.data.message}`,
|
|
3953
|
+
},
|
|
3954
|
+
};
|
|
3955
|
+
break;
|
|
3956
|
+
}
|
|
3957
|
+
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
3958
|
+
const commitCommand = await execAsync(gitCommitCommandFn({
|
|
3959
|
+
...perContextInput,
|
|
3960
|
+
commitMessage,
|
|
3961
|
+
}), { cwd: workspacePath });
|
|
3962
|
+
logger.verbose(`commitCommand ${JSON.stringify(commitCommand)}`);
|
|
3963
|
+
if (!commitCommand.success) {
|
|
3964
|
+
logger.error(formatExecFailureMessage({
|
|
3965
|
+
label: `git commit for context ${context.name}`,
|
|
3966
|
+
failure: commitCommand,
|
|
3967
|
+
}));
|
|
3968
|
+
}
|
|
3712
3969
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
});
|
|
3723
|
-
const perContextInput = {
|
|
3724
|
-
...injectedGitAndWorkspaceFnsInput,
|
|
3725
|
-
context,
|
|
3726
|
-
};
|
|
3727
|
-
const gitAddCommand = await execAsync(gitAddCommandFn(perContextInput), { cwd: workspacePath });
|
|
3728
|
-
logger.verbose(`gitAddCommand ${JSON.stringify(gitAddCommand)}`);
|
|
3729
|
-
if (!gitAddCommand.success) {
|
|
3730
|
-
return set(gitAddCommand, ['data', 'message'], `Failed to add the changes for context ${context.name}: ${gitAddCommand.data.message}`);
|
|
3731
|
-
}
|
|
3732
|
-
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
3733
|
-
const commitCommand = await execAsync(gitCommitCommandFn({
|
|
3734
|
-
...perContextInput,
|
|
3735
|
-
commitMessage,
|
|
3736
|
-
}), { cwd: workspacePath });
|
|
3737
|
-
logger.verbose(`commitCommand ${JSON.stringify(commitCommand)}`);
|
|
3738
|
-
if (!commitCommand.success) {
|
|
3739
|
-
logger.error(formatExecFailureMessage({
|
|
3740
|
-
label: `git commit for context ${context.name}`,
|
|
3741
|
-
failure: commitCommand,
|
|
3742
|
-
}));
|
|
3970
|
+
if (!runFailure) {
|
|
3971
|
+
const pushCommand = await execAsync(gitPushCommandFn(injectedGitAndWorkspaceFnsInput), { cwd: workspacePath });
|
|
3972
|
+
logger.verbose(`pushCommand ${JSON.stringify(pushCommand)}`);
|
|
3973
|
+
if (!pushCommand.success) {
|
|
3974
|
+
logger.error(formatExecFailureMessage({
|
|
3975
|
+
label: `git push on branch ${branchName}`,
|
|
3976
|
+
failure: pushCommand,
|
|
3977
|
+
}));
|
|
3978
|
+
}
|
|
3743
3979
|
}
|
|
3744
3980
|
}
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3981
|
+
finally {
|
|
3982
|
+
const teardownWorkspaceCommand = await teardownWorkspaceFn(injectedGitAndWorkspaceFnsInput);
|
|
3983
|
+
logger.verbose(`teardownWorkspaceCommand ${teardownWorkspaceCommand}`);
|
|
3984
|
+
if (teardownWorkspaceCommand) {
|
|
3985
|
+
const teardownWorkspaceCommandExec = await execAsync(teardownWorkspaceCommand, { cwd: workspacePath });
|
|
3986
|
+
logger.verbose(`teardownWorkspaceCommandExec ${JSON.stringify(teardownWorkspaceCommandExec)}`);
|
|
3987
|
+
if (!teardownWorkspaceCommandExec.success) {
|
|
3988
|
+
if (runFailure) {
|
|
3989
|
+
logger.error(formatExecFailureMessage({
|
|
3990
|
+
label: 'teardown workspace',
|
|
3991
|
+
failure: teardownWorkspaceCommandExec,
|
|
3992
|
+
}));
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
runFailure = {
|
|
3996
|
+
success: false,
|
|
3997
|
+
data: {
|
|
3998
|
+
message: `Failed to teardown the workspace: ${teardownWorkspaceCommandExec.data.message}`,
|
|
3999
|
+
reason: 'workspaceTeardownFailed',
|
|
4000
|
+
},
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
3760
4004
|
}
|
|
3761
4005
|
}
|
|
4006
|
+
if (runFailure) {
|
|
4007
|
+
return runFailure;
|
|
4008
|
+
}
|
|
3762
4009
|
return success({
|
|
3763
4010
|
branchName,
|
|
3764
4011
|
contextNames,
|
|
@@ -4015,7 +4262,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
|
|
|
4015
4262
|
};
|
|
4016
4263
|
|
|
4017
4264
|
async function runLump(input) {
|
|
4018
|
-
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn, gitAddCommandFn = defaultGitAddCommandFn, gitCommitCommandFn = defaultGitCommitCommandFn, gitCommitMessageFn = defaultGitCommitMessageFn, gitPushCommandFn = defaultGitPushCommandFn, numberOfContextsPerBranch = 1, projectRoot, steps, setupFn = () => ({ contextRunState: {} }), setupWorkspaceFn = defaultSetupWorkspaceFn, teardownFn = () => undefined, teardownWorkspaceFn = defaultTeardownWorkspaceFn, getKeepHistoryFilePathFn = () => undefined, logger: loggerInput, } = input;
|
|
4265
|
+
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn, gitAddCommandFn = defaultGitAddCommandFn, gitCommitCommandFn = defaultGitCommitCommandFn, gitCommitMessageFn = defaultGitCommitMessageFn, gitPushCommandFn = defaultGitPushCommandFn, numberOfContextsPerBranch = 1, projectRoot, steps, setupFn = () => ({ contextRunState: {} }), setupWorkspaceFn = defaultSetupWorkspaceFn, teardownFn = () => undefined, teardownWorkspaceFn = defaultTeardownWorkspaceFn, getKeepHistoryFilePathFn = () => undefined, logger: loggerInput, signal, } = input;
|
|
4019
4266
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4020
4267
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
4021
4268
|
const contextListToDoResult = await getToDoContextList({
|
|
@@ -4061,6 +4308,7 @@ async function runLump(input) {
|
|
|
4061
4308
|
teardownWorkspaceFn,
|
|
4062
4309
|
logger,
|
|
4063
4310
|
getKeepHistoryFilePathFn,
|
|
4311
|
+
signal,
|
|
4064
4312
|
});
|
|
4065
4313
|
if (!executeStepsResult.success) {
|
|
4066
4314
|
return set(executeStepsResult, ['data', 'message'], "Error in runLump: Failed to execute steps for context list. Original Error: " + executeStepsResult.data.message);
|
|
@@ -4070,5 +4318,5 @@ async function runLump(input) {
|
|
|
4070
4318
|
});
|
|
4071
4319
|
}
|
|
4072
4320
|
|
|
4073
|
-
export { appendHistoryEntry, collectStepsForContext, contextStatus, createConsoleLogger, defaultGitAddCommandFn, defaultGitCommitCommandFn, defaultGitCommitMessageFn, defaultGitPushCommandFn, defaultSetupWorkspaceFn, defaultSetupWorkspaceFnWithWorktree, defaultTeardownWorkspaceFn, defaultTeardownWorkspaceFnWithWorktree, execAsync, execBinary, executeStepsForContextList, failure, formatExecFailureMessage, getCodeBasePaths, getContextStatus, getToDoContextList, historyFormatFromPath, parseGitLogHashSubjectLines, pathExists, readHistoryFile, resolveSpawnExecutable, runLump, set, shellSingleQuote, success, validateContextListNames, windowsNpmCmdShimBody, writeHistoryFile };
|
|
4321
|
+
export { appendHistoryEntry, collectStepsForContext, contextStatus, createConsoleLogger, defaultGitAddCommandFn, defaultGitCommitCommandFn, defaultGitCommitMessageFn, defaultGitPushCommandFn, defaultSetupWorkspaceFn, defaultSetupWorkspaceFnWithWorktree, defaultTeardownWorkspaceFn, defaultTeardownWorkspaceFnWithWorktree, execAsync, execBinary, executeStepsForContextList, failure, formatExecFailureMessage, getCodeBasePaths, getContextStatus, getToDoContextList, historyFormatFromPath, isProcessAlive, killProcessTree, nodeErrnoCode, parseGitLogHashSubjectLines, pathExists, readHistoryFile, resolveSpawnExecutable, runLump, set, shellSingleQuote, success, validateContextListNames, windowsNpmCmdShimBody, writeHistoryFile };
|
|
4074
4322
|
//# sourceMappingURL=index.js.map
|