@lumpcode/core 0.0.15 → 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.cjs
CHANGED
|
@@ -3424,6 +3424,152 @@ function parseGitLogHashSubjectLines(stdout) {
|
|
|
3424
3424
|
});
|
|
3425
3425
|
}
|
|
3426
3426
|
|
|
3427
|
+
/** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
|
|
3428
|
+
function nodeErrnoCode(error) {
|
|
3429
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
3430
|
+
const code = error.code;
|
|
3431
|
+
return typeof code === 'string' ? code : undefined;
|
|
3432
|
+
}
|
|
3433
|
+
return undefined;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
/** Returns whether a process id is still running (signal 0 probe). */
|
|
3437
|
+
function isProcessAlive(pid, options) {
|
|
3438
|
+
const onProbeError = options?.onProbeError ?? 'throw';
|
|
3439
|
+
try {
|
|
3440
|
+
process.kill(pid, 0);
|
|
3441
|
+
return true;
|
|
3442
|
+
}
|
|
3443
|
+
catch (error) {
|
|
3444
|
+
if (nodeErrnoCode(error) === 'ESRCH')
|
|
3445
|
+
return false;
|
|
3446
|
+
if (onProbeError === 'throw')
|
|
3447
|
+
throw error;
|
|
3448
|
+
return onProbeError === 'alive';
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
const execFileAsync = node_util.promisify(node_child_process.execFile);
|
|
3453
|
+
function sleep(ms) {
|
|
3454
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3455
|
+
}
|
|
3456
|
+
async function listUnixProcessTreePids(rootPid) {
|
|
3457
|
+
const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,ppid=']);
|
|
3458
|
+
const childrenByParent = new Map();
|
|
3459
|
+
for (const line of stdout.split('\n')) {
|
|
3460
|
+
const trimmed = line.trim();
|
|
3461
|
+
if (!trimmed)
|
|
3462
|
+
continue;
|
|
3463
|
+
const parts = trimmed.split(/\s+/);
|
|
3464
|
+
if (parts.length < 2)
|
|
3465
|
+
continue;
|
|
3466
|
+
const pid = Number.parseInt(parts[0] ?? '', 10);
|
|
3467
|
+
const ppid = Number.parseInt(parts[1] ?? '', 10);
|
|
3468
|
+
if (Number.isNaN(pid) || Number.isNaN(ppid))
|
|
3469
|
+
continue;
|
|
3470
|
+
const siblings = childrenByParent.get(ppid) ?? [];
|
|
3471
|
+
siblings.push(pid);
|
|
3472
|
+
childrenByParent.set(ppid, siblings);
|
|
3473
|
+
}
|
|
3474
|
+
const ordered = [];
|
|
3475
|
+
const queue = [rootPid];
|
|
3476
|
+
while (queue.length > 0) {
|
|
3477
|
+
const pid = queue.shift();
|
|
3478
|
+
if (pid === undefined)
|
|
3479
|
+
break;
|
|
3480
|
+
ordered.push(pid);
|
|
3481
|
+
const children = childrenByParent.get(pid) ?? [];
|
|
3482
|
+
for (const childPid of children) {
|
|
3483
|
+
queue.push(childPid);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
return ordered;
|
|
3487
|
+
}
|
|
3488
|
+
function killPid(pid, signal) {
|
|
3489
|
+
try {
|
|
3490
|
+
process.kill(pid, signal);
|
|
3491
|
+
}
|
|
3492
|
+
catch (error) {
|
|
3493
|
+
const code = nodeErrnoCode(error);
|
|
3494
|
+
if (code !== 'ESRCH') {
|
|
3495
|
+
throw error;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
async function killUnixProcessTreeImmediate(rootPid) {
|
|
3500
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3501
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3502
|
+
killPid(pids[index], 'SIGKILL');
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
async function killUnixProcessTreeWithGrace(rootPid, graceMs) {
|
|
3506
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3507
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3508
|
+
killPid(pids[index], 'SIGTERM');
|
|
3509
|
+
}
|
|
3510
|
+
const deadline = Date.now() + graceMs;
|
|
3511
|
+
while (Date.now() < deadline) {
|
|
3512
|
+
const anyAlive = pids.some((pid) => isProcessAlive(pid, { onProbeError: 'dead' }));
|
|
3513
|
+
if (!anyAlive) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
await sleep(25);
|
|
3517
|
+
}
|
|
3518
|
+
const remaining = await listUnixProcessTreePids(rootPid);
|
|
3519
|
+
for (let index = remaining.length - 1; index >= 0; index -= 1) {
|
|
3520
|
+
killPid(remaining[index], 'SIGKILL');
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function killWindowsProcessTree(rootPid) {
|
|
3524
|
+
try {
|
|
3525
|
+
await execFileAsync('taskkill', ['/PID', String(rootPid), '/T', '/F'], {
|
|
3526
|
+
windowsHide: true,
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
catch (error) {
|
|
3530
|
+
const stderr = error && typeof error === 'object' && 'stderr' in error
|
|
3531
|
+
? String(error.stderr ?? '')
|
|
3532
|
+
: '';
|
|
3533
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3534
|
+
const combined = `${message}\n${stderr}`;
|
|
3535
|
+
if (/not found|no running instance|ne existe pas|introuvable/i.test(combined)) {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
// taskkill can fail to terminate some descendants while the root exits (SEA/agent trees).
|
|
3539
|
+
if (!isProcessAlive(rootPid)) {
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
throw error;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Kill a process and its descendants.
|
|
3547
|
+
* `graceMs` default 0 → immediate SIGKILL / taskkill /T /F.
|
|
3548
|
+
* When `graceMs > 0` (Unix), SIGTERM first, then SIGKILL after the grace window.
|
|
3549
|
+
*/
|
|
3550
|
+
async function killProcessTree(input) {
|
|
3551
|
+
const { pid, graceMs = 0 } = input;
|
|
3552
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
3553
|
+
return failure(`Invalid pid: ${pid}`);
|
|
3554
|
+
}
|
|
3555
|
+
try {
|
|
3556
|
+
if (process.platform === 'win32') {
|
|
3557
|
+
await killWindowsProcessTree(pid);
|
|
3558
|
+
}
|
|
3559
|
+
else if (graceMs > 0) {
|
|
3560
|
+
await killUnixProcessTreeWithGrace(pid, graceMs);
|
|
3561
|
+
}
|
|
3562
|
+
else {
|
|
3563
|
+
await killUnixProcessTreeImmediate(pid);
|
|
3564
|
+
}
|
|
3565
|
+
return success(undefined);
|
|
3566
|
+
}
|
|
3567
|
+
catch (error) {
|
|
3568
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3569
|
+
return failure(`Could not kill process tree for pid ${pid}: ${message}`);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3427
3573
|
const execAsyncBase = node_util.promisify(node_child_process.exec);
|
|
3428
3574
|
async function execAsync(command, options) {
|
|
3429
3575
|
const { stdout, stderr, hasErrored } = await execAsyncBase(command, { cwd: options?.cwd })
|
|
@@ -3453,38 +3599,86 @@ async function execAsync(command, options) {
|
|
|
3453
3599
|
});
|
|
3454
3600
|
}
|
|
3455
3601
|
|
|
3456
|
-
function execBinary(
|
|
3602
|
+
function execBinary(input) {
|
|
3603
|
+
const { binaryPath, args, timeoutMillis = 1000 * 60 * 10, cwd, env, stdio, signal, killGraceMs = 5000, } = input;
|
|
3457
3604
|
return new Promise((resolve) => {
|
|
3458
3605
|
let settled = false;
|
|
3606
|
+
let canceling = false;
|
|
3607
|
+
let child;
|
|
3608
|
+
let stdout = '';
|
|
3609
|
+
let stderr = '';
|
|
3610
|
+
let timeout;
|
|
3459
3611
|
const finish = (result) => {
|
|
3460
3612
|
if (settled)
|
|
3461
3613
|
return;
|
|
3462
3614
|
settled = true;
|
|
3463
|
-
|
|
3615
|
+
if (timeout !== undefined)
|
|
3616
|
+
clearTimeout(timeout);
|
|
3617
|
+
if (signal) {
|
|
3618
|
+
signal.removeEventListener('abort', onAbort);
|
|
3619
|
+
}
|
|
3464
3620
|
resolve(result);
|
|
3465
3621
|
};
|
|
3466
|
-
const
|
|
3622
|
+
const cancelWith = async (reason, message) => {
|
|
3623
|
+
if (settled || canceling)
|
|
3624
|
+
return;
|
|
3625
|
+
canceling = true;
|
|
3626
|
+
const pid = child?.pid;
|
|
3627
|
+
if (pid != null) {
|
|
3628
|
+
await killProcessTree({ pid, graceMs: killGraceMs });
|
|
3629
|
+
}
|
|
3467
3630
|
finish(failure({
|
|
3468
|
-
message
|
|
3631
|
+
message,
|
|
3469
3632
|
binaryPath,
|
|
3470
3633
|
args,
|
|
3634
|
+
stdout,
|
|
3635
|
+
stderr,
|
|
3636
|
+
reason,
|
|
3471
3637
|
}));
|
|
3472
|
-
}
|
|
3638
|
+
};
|
|
3639
|
+
const onAbort = () => {
|
|
3640
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3641
|
+
};
|
|
3642
|
+
if (signal?.aborted) {
|
|
3643
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
if (signal) {
|
|
3647
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3648
|
+
}
|
|
3473
3649
|
const { executable: resolvedExecutable, args: resolvedArgs } = resolveSpawnExecutable(binaryPath, args);
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3650
|
+
const spawnOptions = {
|
|
3651
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
3652
|
+
...(env !== undefined ? { env } : {}),
|
|
3653
|
+
...(stdio !== undefined ? { stdio } : {}),
|
|
3654
|
+
};
|
|
3655
|
+
child = node_child_process.spawn(resolvedExecutable, resolvedArgs, spawnOptions);
|
|
3656
|
+
// Start the timeout only after the OS has spawned the process so
|
|
3657
|
+
// timeoutMillis measures command runtime, not spawn/setup overhead.
|
|
3658
|
+
child.on('spawn', () => {
|
|
3659
|
+
if (settled || canceling)
|
|
3660
|
+
return;
|
|
3661
|
+
timeout = setTimeout(() => {
|
|
3662
|
+
void cancelWith('timeout', `Process timed out after ${timeoutMillis} milliseconds`);
|
|
3663
|
+
}, timeoutMillis);
|
|
3664
|
+
});
|
|
3476
3665
|
child.stdout?.on('data', (data) => { stdout += data; });
|
|
3477
3666
|
child.stderr?.on('data', (data) => { stderr += data; });
|
|
3478
3667
|
child.on('error', (err) => {
|
|
3668
|
+
if (settled || canceling)
|
|
3669
|
+
return;
|
|
3479
3670
|
finish(failure({
|
|
3480
3671
|
message: err.message,
|
|
3481
3672
|
binaryPath,
|
|
3482
3673
|
args,
|
|
3483
3674
|
stdout,
|
|
3484
3675
|
stderr,
|
|
3676
|
+
reason: 'spawn',
|
|
3485
3677
|
}));
|
|
3486
3678
|
});
|
|
3487
3679
|
child.on('close', (code) => {
|
|
3680
|
+
if (settled || canceling)
|
|
3681
|
+
return;
|
|
3488
3682
|
if (code === 0) {
|
|
3489
3683
|
finish(success({ stdout, stderr }));
|
|
3490
3684
|
return;
|
|
@@ -3496,6 +3690,7 @@ function execBinary(binaryPath, args, timeoutMillis = 1000 * 60 * 10, options) {
|
|
|
3496
3690
|
code: code ?? undefined,
|
|
3497
3691
|
stdout,
|
|
3498
3692
|
stderr,
|
|
3693
|
+
reason: 'exit',
|
|
3499
3694
|
}));
|
|
3500
3695
|
});
|
|
3501
3696
|
});
|
|
@@ -3571,7 +3766,7 @@ async function collectStepsForContext(params) {
|
|
|
3571
3766
|
return collectedSteps;
|
|
3572
3767
|
}
|
|
3573
3768
|
|
|
3574
|
-
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, }) {
|
|
3769
|
+
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
|
|
3575
3770
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
3576
3771
|
const contextNames = contextList.map(context => context.name);
|
|
3577
3772
|
logger.verbose(`contextNames ${JSON.stringify(contextNames)}`);
|
|
@@ -3606,180 +3801,232 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3606
3801
|
const gitStatusCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3607
3802
|
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusCommand.data)}`);
|
|
3608
3803
|
injectedGitAndWorkspaceFnsInput.workspacePath = workspacePath;
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
if (typeof step === 'function') {
|
|
3632
|
-
subSteps =
|
|
3804
|
+
let runFailure;
|
|
3805
|
+
try {
|
|
3806
|
+
for (let i = 0; i < contextList.length; i++) {
|
|
3807
|
+
const context = contextList[i];
|
|
3808
|
+
logger.info(contextList.length > 1
|
|
3809
|
+
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
3810
|
+
: `Running context "${context.name}"`);
|
|
3811
|
+
const setupResult = await setupFn({
|
|
3812
|
+
contextList,
|
|
3813
|
+
lumpVariables,
|
|
3814
|
+
currentContextIndex: i,
|
|
3815
|
+
});
|
|
3816
|
+
const contextRunState = setupResult?.contextRunState || {};
|
|
3817
|
+
let stepWalkFailure;
|
|
3818
|
+
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3819
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3820
|
+
if (stepWalkFailure) {
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
const step = stepsToExec[stepIndex];
|
|
3824
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3825
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3826
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3827
|
+
let subSteps = [];
|
|
3828
|
+
if (typeof step === 'function') {
|
|
3829
|
+
subSteps = await step({
|
|
3830
|
+
context,
|
|
3831
|
+
stepIndex: compositeStepIndex,
|
|
3832
|
+
contextRunState,
|
|
3833
|
+
lumpVariables,
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
else {
|
|
3837
|
+
subSteps = step;
|
|
3838
|
+
}
|
|
3839
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3840
|
+
continue;
|
|
3841
|
+
}
|
|
3842
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3843
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3844
|
+
const prompt = promptFn
|
|
3845
|
+
? await promptFn({
|
|
3633
3846
|
context,
|
|
3634
3847
|
stepIndex: compositeStepIndex,
|
|
3635
3848
|
contextRunState,
|
|
3636
3849
|
lumpVariables,
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
}
|
|
3642
|
-
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3643
|
-
continue;
|
|
3644
|
-
}
|
|
3645
|
-
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3646
|
-
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3647
|
-
const prompt = promptFn
|
|
3648
|
-
? await promptFn({
|
|
3850
|
+
stepVariables,
|
|
3851
|
+
})
|
|
3852
|
+
: '';
|
|
3853
|
+
const command = await commandFn({
|
|
3649
3854
|
context,
|
|
3855
|
+
prompt,
|
|
3650
3856
|
stepIndex: compositeStepIndex,
|
|
3651
3857
|
contextRunState,
|
|
3652
3858
|
lumpVariables,
|
|
3653
3859
|
stepVariables,
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
const command = await commandFn({
|
|
3657
|
-
context,
|
|
3658
|
-
prompt,
|
|
3659
|
-
stepIndex: compositeStepIndex,
|
|
3660
|
-
contextRunState,
|
|
3661
|
-
lumpVariables,
|
|
3662
|
-
stepVariables,
|
|
3663
|
-
projectRoot,
|
|
3664
|
-
workspacePath,
|
|
3665
|
-
});
|
|
3666
|
-
let commandResult = '';
|
|
3667
|
-
let commandSucceeded = true;
|
|
3668
|
-
if (command != null) {
|
|
3669
|
-
const { executable, args, env } = command;
|
|
3670
|
-
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3671
|
-
if (env != null) {
|
|
3672
|
-
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3673
|
-
}
|
|
3674
|
-
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3675
|
-
const commandExec = await execBinary(executable, args, timeoutMillis, {
|
|
3676
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3677
|
-
cwd: workspacePath,
|
|
3678
|
-
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3860
|
+
projectRoot,
|
|
3861
|
+
workspacePath,
|
|
3679
3862
|
});
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3863
|
+
let commandResult = '';
|
|
3864
|
+
let commandSucceeded = true;
|
|
3865
|
+
if (command != null) {
|
|
3866
|
+
const { executable, args, env } = command;
|
|
3867
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3868
|
+
if (env != null) {
|
|
3869
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3870
|
+
}
|
|
3871
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3872
|
+
const commandExec = await execBinary({
|
|
3873
|
+
binaryPath: executable,
|
|
3874
|
+
args,
|
|
3875
|
+
timeoutMillis,
|
|
3876
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3877
|
+
cwd: workspacePath,
|
|
3878
|
+
signal,
|
|
3879
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3880
|
+
});
|
|
3881
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
3882
|
+
if (!commandExec.success) {
|
|
3883
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
3884
|
+
if (aborted || !continueOnError) {
|
|
3885
|
+
stepWalkFailure = {
|
|
3886
|
+
success: false,
|
|
3887
|
+
data: {
|
|
3888
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
3889
|
+
reason: 'stepWalkFailed',
|
|
3890
|
+
},
|
|
3891
|
+
};
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
commandSucceeded = false;
|
|
3895
|
+
commandResult = (commandExec.data.stdout
|
|
3896
|
+
|| commandExec.data.stderr
|
|
3897
|
+
|| commandExec.data.message
|
|
3898
|
+
|| '').toString();
|
|
3899
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3900
|
+
}
|
|
3901
|
+
else {
|
|
3902
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3903
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3904
|
+
}
|
|
3905
|
+
if (commandSucceeded) {
|
|
3906
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3907
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3685
3908
|
}
|
|
3686
|
-
commandSucceeded = false;
|
|
3687
|
-
commandResult = (commandExec.data.stdout
|
|
3688
|
-
|| commandExec.data.stderr
|
|
3689
|
-
|| commandExec.data.message
|
|
3690
|
-
|| '').toString();
|
|
3691
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3692
3909
|
}
|
|
3693
|
-
|
|
3694
|
-
commandResult
|
|
3695
|
-
|
|
3910
|
+
const postCommandExecFnInput = {
|
|
3911
|
+
commandResult,
|
|
3912
|
+
commandSucceeded,
|
|
3913
|
+
context,
|
|
3914
|
+
prompt,
|
|
3915
|
+
stepIndex: compositeStepIndex,
|
|
3916
|
+
contextRunState,
|
|
3917
|
+
lumpVariables,
|
|
3918
|
+
stepVariables,
|
|
3919
|
+
projectRoot,
|
|
3920
|
+
};
|
|
3921
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3922
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3923
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3924
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3925
|
+
const appendResult = await appendHistoryEntry({
|
|
3926
|
+
filePath: keepHistoryFilePath,
|
|
3927
|
+
entry: postCommandExecFnInput,
|
|
3928
|
+
});
|
|
3929
|
+
if (!appendResult.success) {
|
|
3930
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3931
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3932
|
+
}
|
|
3696
3933
|
}
|
|
3697
|
-
if (
|
|
3698
|
-
const
|
|
3699
|
-
|
|
3934
|
+
if (postCommandExecFn) {
|
|
3935
|
+
const returnedSteps = await postCommandExecFn(postCommandExecFnInput);
|
|
3936
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
3937
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3938
|
+
}
|
|
3700
3939
|
}
|
|
3701
3940
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3714
|
-
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3715
|
-
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3716
|
-
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3717
|
-
const appendResult = await appendHistoryEntry({
|
|
3718
|
-
filePath: keepHistoryFilePath,
|
|
3719
|
-
entry: postCommandExecFnInput,
|
|
3941
|
+
}
|
|
3942
|
+
try {
|
|
3943
|
+
await walkAndExecuteSteps(steps, []);
|
|
3944
|
+
}
|
|
3945
|
+
finally {
|
|
3946
|
+
try {
|
|
3947
|
+
await teardownFn({
|
|
3948
|
+
lumpVariables,
|
|
3949
|
+
contextList,
|
|
3950
|
+
currentContextIndex: i,
|
|
3951
|
+
contextRunState,
|
|
3720
3952
|
});
|
|
3721
|
-
if (!appendResult.success) {
|
|
3722
|
-
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3723
|
-
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3724
|
-
}
|
|
3725
3953
|
}
|
|
3726
|
-
|
|
3727
|
-
const
|
|
3728
|
-
|
|
3729
|
-
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3730
|
-
}
|
|
3954
|
+
catch (error) {
|
|
3955
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3956
|
+
logger.error(`Failed to run teardownFn: ${message}`);
|
|
3731
3957
|
}
|
|
3732
3958
|
}
|
|
3959
|
+
if (stepWalkFailure) {
|
|
3960
|
+
runFailure = stepWalkFailure;
|
|
3961
|
+
break;
|
|
3962
|
+
}
|
|
3963
|
+
const perContextInput = {
|
|
3964
|
+
...injectedGitAndWorkspaceFnsInput,
|
|
3965
|
+
context,
|
|
3966
|
+
};
|
|
3967
|
+
const gitAddCommand = await execAsync(gitAddCommandFn(perContextInput), { cwd: workspacePath });
|
|
3968
|
+
logger.verbose(`gitAddCommand ${JSON.stringify(gitAddCommand)}`);
|
|
3969
|
+
if (!gitAddCommand.success) {
|
|
3970
|
+
runFailure = {
|
|
3971
|
+
success: false,
|
|
3972
|
+
data: {
|
|
3973
|
+
message: `Failed to add the changes for context ${context.name}: ${gitAddCommand.data.message}`,
|
|
3974
|
+
},
|
|
3975
|
+
};
|
|
3976
|
+
break;
|
|
3977
|
+
}
|
|
3978
|
+
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
3979
|
+
const commitCommand = await execAsync(gitCommitCommandFn({
|
|
3980
|
+
...perContextInput,
|
|
3981
|
+
commitMessage,
|
|
3982
|
+
}), { cwd: workspacePath });
|
|
3983
|
+
logger.verbose(`commitCommand ${JSON.stringify(commitCommand)}`);
|
|
3984
|
+
if (!commitCommand.success) {
|
|
3985
|
+
logger.error(formatExecFailureMessage({
|
|
3986
|
+
label: `git commit for context ${context.name}`,
|
|
3987
|
+
failure: commitCommand,
|
|
3988
|
+
}));
|
|
3989
|
+
}
|
|
3733
3990
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
});
|
|
3744
|
-
const perContextInput = {
|
|
3745
|
-
...injectedGitAndWorkspaceFnsInput,
|
|
3746
|
-
context,
|
|
3747
|
-
};
|
|
3748
|
-
const gitAddCommand = await execAsync(gitAddCommandFn(perContextInput), { cwd: workspacePath });
|
|
3749
|
-
logger.verbose(`gitAddCommand ${JSON.stringify(gitAddCommand)}`);
|
|
3750
|
-
if (!gitAddCommand.success) {
|
|
3751
|
-
return set(gitAddCommand, ['data', 'message'], `Failed to add the changes for context ${context.name}: ${gitAddCommand.data.message}`);
|
|
3752
|
-
}
|
|
3753
|
-
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
3754
|
-
const commitCommand = await execAsync(gitCommitCommandFn({
|
|
3755
|
-
...perContextInput,
|
|
3756
|
-
commitMessage,
|
|
3757
|
-
}), { cwd: workspacePath });
|
|
3758
|
-
logger.verbose(`commitCommand ${JSON.stringify(commitCommand)}`);
|
|
3759
|
-
if (!commitCommand.success) {
|
|
3760
|
-
logger.error(formatExecFailureMessage({
|
|
3761
|
-
label: `git commit for context ${context.name}`,
|
|
3762
|
-
failure: commitCommand,
|
|
3763
|
-
}));
|
|
3991
|
+
if (!runFailure) {
|
|
3992
|
+
const pushCommand = await execAsync(gitPushCommandFn(injectedGitAndWorkspaceFnsInput), { cwd: workspacePath });
|
|
3993
|
+
logger.verbose(`pushCommand ${JSON.stringify(pushCommand)}`);
|
|
3994
|
+
if (!pushCommand.success) {
|
|
3995
|
+
logger.error(formatExecFailureMessage({
|
|
3996
|
+
label: `git push on branch ${branchName}`,
|
|
3997
|
+
failure: pushCommand,
|
|
3998
|
+
}));
|
|
3999
|
+
}
|
|
3764
4000
|
}
|
|
3765
4001
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
4002
|
+
finally {
|
|
4003
|
+
const teardownWorkspaceCommand = await teardownWorkspaceFn(injectedGitAndWorkspaceFnsInput);
|
|
4004
|
+
logger.verbose(`teardownWorkspaceCommand ${teardownWorkspaceCommand}`);
|
|
4005
|
+
if (teardownWorkspaceCommand) {
|
|
4006
|
+
const teardownWorkspaceCommandExec = await execAsync(teardownWorkspaceCommand, { cwd: workspacePath });
|
|
4007
|
+
logger.verbose(`teardownWorkspaceCommandExec ${JSON.stringify(teardownWorkspaceCommandExec)}`);
|
|
4008
|
+
if (!teardownWorkspaceCommandExec.success) {
|
|
4009
|
+
if (runFailure) {
|
|
4010
|
+
logger.error(formatExecFailureMessage({
|
|
4011
|
+
label: 'teardown workspace',
|
|
4012
|
+
failure: teardownWorkspaceCommandExec,
|
|
4013
|
+
}));
|
|
4014
|
+
}
|
|
4015
|
+
else {
|
|
4016
|
+
runFailure = {
|
|
4017
|
+
success: false,
|
|
4018
|
+
data: {
|
|
4019
|
+
message: `Failed to teardown the workspace: ${teardownWorkspaceCommandExec.data.message}`,
|
|
4020
|
+
reason: 'workspaceTeardownFailed',
|
|
4021
|
+
},
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
3781
4025
|
}
|
|
3782
4026
|
}
|
|
4027
|
+
if (runFailure) {
|
|
4028
|
+
return runFailure;
|
|
4029
|
+
}
|
|
3783
4030
|
return success({
|
|
3784
4031
|
branchName,
|
|
3785
4032
|
contextNames,
|
|
@@ -4036,7 +4283,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
|
|
|
4036
4283
|
};
|
|
4037
4284
|
|
|
4038
4285
|
async function runLump(input) {
|
|
4039
|
-
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;
|
|
4286
|
+
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;
|
|
4040
4287
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4041
4288
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
4042
4289
|
const contextListToDoResult = await getToDoContextList({
|
|
@@ -4082,6 +4329,7 @@ async function runLump(input) {
|
|
|
4082
4329
|
teardownWorkspaceFn,
|
|
4083
4330
|
logger,
|
|
4084
4331
|
getKeepHistoryFilePathFn,
|
|
4332
|
+
signal,
|
|
4085
4333
|
});
|
|
4086
4334
|
if (!executeStepsResult.success) {
|
|
4087
4335
|
return set(executeStepsResult, ['data', 'message'], "Error in runLump: Failed to execute steps for context list. Original Error: " + executeStepsResult.data.message);
|
|
@@ -4112,6 +4360,9 @@ exports.getCodeBasePaths = getCodeBasePaths;
|
|
|
4112
4360
|
exports.getContextStatus = getContextStatus;
|
|
4113
4361
|
exports.getToDoContextList = getToDoContextList;
|
|
4114
4362
|
exports.historyFormatFromPath = historyFormatFromPath;
|
|
4363
|
+
exports.isProcessAlive = isProcessAlive;
|
|
4364
|
+
exports.killProcessTree = killProcessTree;
|
|
4365
|
+
exports.nodeErrnoCode = nodeErrnoCode;
|
|
4115
4366
|
exports.parseGitLogHashSubjectLines = parseGitLogHashSubjectLines;
|
|
4116
4367
|
exports.pathExists = pathExists;
|
|
4117
4368
|
exports.readHistoryFile = readHistoryFile;
|