@lumpcode/core 0.1.0 → 0.2.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/README.md +18 -16
- package/dist/helpers/execAsync/main.d.ts +14 -9
- package/dist/helpers/execAsync/main.d.ts.map +1 -1
- package/dist/helpers/executeStepsForContextList/main.d.ts +4 -9
- package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
- package/dist/helpers/getContextStatus/main.d.ts.map +1 -1
- package/dist/index.cjs +303 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +299 -175
- package/dist/index.js.map +1 -1
- package/dist/testing/processTreeTestHelpers.d.ts +3 -0
- package/dist/testing/processTreeTestHelpers.d.ts.map +1 -1
- package/dist/types/ExecuteStepsFailureReason.d.ts +1 -1
- package/dist/types/ExecuteStepsFailureReason.d.ts.map +1 -1
- package/dist/types/GitAddCommitFn.d.ts +16 -0
- package/dist/types/GitAddCommitFn.d.ts.map +1 -0
- package/dist/types/GitPushFn.d.ts +12 -0
- package/dist/types/GitPushFn.d.ts.map +1 -0
- package/dist/types/PostCommandExecFn.d.ts +2 -0
- package/dist/types/PostCommandExecFn.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/usages/runLump/defaultInjectedFns.d.ts +3 -4
- package/dist/usages/runLump/defaultInjectedFns.d.ts.map +1 -1
- package/dist/usages/runLump/index.d.ts +1 -1
- package/dist/usages/runLump/index.d.ts.map +1 -1
- package/dist/usages/runLump/main.d.ts +3 -4
- package/dist/usages/runLump/main.d.ts.map +1 -1
- package/dist/utils/commitMessageIncludesMarker/index.d.ts +2 -0
- package/dist/utils/commitMessageIncludesMarker/index.d.ts.map +1 -0
- package/dist/utils/commitMessageIncludesMarker/main.d.ts +3 -0
- package/dist/utils/commitMessageIncludesMarker/main.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/parseGitLogHashBodyRecords/index.d.ts +2 -0
- package/dist/utils/parseGitLogHashBodyRecords/index.d.ts.map +1 -0
- package/dist/utils/parseGitLogHashBodyRecords/main.d.ts +8 -0
- package/dist/utils/parseGitLogHashBodyRecords/main.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3424,6 +3424,40 @@ function parseGitLogHashSubjectLines(stdout) {
|
|
|
3424
3424
|
});
|
|
3425
3425
|
}
|
|
3426
3426
|
|
|
3427
|
+
const GIT_LOG_HASH_BODY_FORMAT = '%x1e%H%x00%B';
|
|
3428
|
+
const RECORD_SEPARATOR = '\x1e';
|
|
3429
|
+
const HASH_MESSAGE_SEPARATOR = '\x00';
|
|
3430
|
+
/** Parses `git log --format='%x1e%H%x00%B'` stdout into commit hash and full-message pairs. */
|
|
3431
|
+
function parseGitLogHashBodyRecords(stdout) {
|
|
3432
|
+
const records = [];
|
|
3433
|
+
for (const record of stdout.split(RECORD_SEPARATOR)) {
|
|
3434
|
+
if (!record)
|
|
3435
|
+
continue;
|
|
3436
|
+
const nul = record.indexOf(HASH_MESSAGE_SEPARATOR);
|
|
3437
|
+
if (nul === -1)
|
|
3438
|
+
continue;
|
|
3439
|
+
const hash = record.slice(0, nul).trim();
|
|
3440
|
+
if (!hash)
|
|
3441
|
+
continue;
|
|
3442
|
+
records.push({
|
|
3443
|
+
hash,
|
|
3444
|
+
message: record.slice(nul + 1),
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
return records;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
const MARKER_CONTINUE = '[A-Za-z0-9_-]';
|
|
3451
|
+
function escapeRegExp(value) {
|
|
3452
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3453
|
+
}
|
|
3454
|
+
/** True when `marker` occurs in `message` and is not a prefix of a longer `[A-Za-z0-9_-]*` token. */
|
|
3455
|
+
function commitMessageIncludesMarker(message, marker) {
|
|
3456
|
+
if (marker.length === 0)
|
|
3457
|
+
return false;
|
|
3458
|
+
return new RegExp(`${escapeRegExp(marker)}(?!${MARKER_CONTINUE})`).test(message);
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3427
3461
|
/** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
|
|
3428
3462
|
function nodeErrnoCode(error) {
|
|
3429
3463
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
@@ -3571,32 +3605,54 @@ async function killProcessTree(input) {
|
|
|
3571
3605
|
}
|
|
3572
3606
|
|
|
3573
3607
|
const execAsyncBase = node_util.promisify(node_child_process.exec);
|
|
3608
|
+
/** Same signal Node's `exec` `timeout` option uses by default. */
|
|
3609
|
+
const EXEC_TIMEOUT_KILL_SIGNAL = 'SIGTERM';
|
|
3610
|
+
function isExecTimeout(error, timeoutMillis) {
|
|
3611
|
+
if (timeoutMillis === undefined) {
|
|
3612
|
+
return false;
|
|
3613
|
+
}
|
|
3614
|
+
if (typeof error !== 'object' || error === null) {
|
|
3615
|
+
return false;
|
|
3616
|
+
}
|
|
3617
|
+
const execError = error;
|
|
3618
|
+
if (execError.killed !== true) {
|
|
3619
|
+
return false;
|
|
3620
|
+
}
|
|
3621
|
+
// Windows has no POSIX signals; Node force-kills and `signal` is often null.
|
|
3622
|
+
if (process.platform === 'win32') {
|
|
3623
|
+
return true;
|
|
3624
|
+
}
|
|
3625
|
+
return execError.signal === EXEC_TIMEOUT_KILL_SIGNAL;
|
|
3626
|
+
}
|
|
3574
3627
|
async function execAsync(command, options) {
|
|
3575
|
-
const
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3628
|
+
const timeoutMillis = options?.timeoutMillis;
|
|
3629
|
+
try {
|
|
3630
|
+
const result = await execAsyncBase(command, {
|
|
3631
|
+
cwd: options?.cwd,
|
|
3632
|
+
...(timeoutMillis !== undefined
|
|
3633
|
+
? { timeout: timeoutMillis, killSignal: EXEC_TIMEOUT_KILL_SIGNAL }
|
|
3634
|
+
: {}),
|
|
3635
|
+
});
|
|
3636
|
+
return success({
|
|
3637
|
+
stdout: String(result.stdout),
|
|
3638
|
+
stderr: String(result.stderr),
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
catch (e) {
|
|
3642
|
+
const timedOut = isExecTimeout(e, timeoutMillis);
|
|
3643
|
+
const reason = timedOut ? 'timeout' : 'exit';
|
|
3587
3644
|
return failure({
|
|
3588
|
-
message:
|
|
3645
|
+
message: timedOut
|
|
3646
|
+
? `Command ${command} timed out after ${timeoutMillis}ms`
|
|
3647
|
+
: `Command ${command} failed with error: ${e}`,
|
|
3648
|
+
reason,
|
|
3589
3649
|
info: {
|
|
3590
3650
|
command,
|
|
3591
|
-
stdout,
|
|
3592
|
-
stderr,
|
|
3651
|
+
stdout: e,
|
|
3652
|
+
stderr: e,
|
|
3593
3653
|
},
|
|
3594
3654
|
});
|
|
3595
3655
|
}
|
|
3596
|
-
return success({
|
|
3597
|
-
stdout,
|
|
3598
|
-
stderr,
|
|
3599
|
-
});
|
|
3600
3656
|
}
|
|
3601
3657
|
|
|
3602
3658
|
function execBinary(input) {
|
|
@@ -3766,28 +3822,85 @@ async function collectStepsForContext(params) {
|
|
|
3766
3822
|
return collectedSteps;
|
|
3767
3823
|
}
|
|
3768
3824
|
|
|
3769
|
-
|
|
3770
|
-
|
|
3825
|
+
const ABORT_STEP_WALK_MESSAGE = 'Process aborted';
|
|
3826
|
+
function stepWalkAbortedFailure() {
|
|
3827
|
+
return {
|
|
3828
|
+
success: false,
|
|
3829
|
+
data: {
|
|
3830
|
+
message: ABORT_STEP_WALK_MESSAGE,
|
|
3831
|
+
reason: 'stepWalkFailed',
|
|
3832
|
+
},
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3835
|
+
function createStepWalkAbortError() {
|
|
3836
|
+
const error = new Error(ABORT_STEP_WALK_MESSAGE);
|
|
3837
|
+
error.name = 'AbortError';
|
|
3838
|
+
return error;
|
|
3839
|
+
}
|
|
3840
|
+
function isStepWalkAbortError(error) {
|
|
3841
|
+
return (typeof error === 'object'
|
|
3842
|
+
&& error !== null
|
|
3843
|
+
&& 'name' in error
|
|
3844
|
+
&& error.name === 'AbortError');
|
|
3845
|
+
}
|
|
3846
|
+
/**
|
|
3847
|
+
* Await user-hook work, but stop waiting once `signal` aborts so the walk can
|
|
3848
|
+
* unwind (locks/teardown). Does not cancel sync busy-loops on the event loop.
|
|
3849
|
+
*/
|
|
3850
|
+
async function awaitUnlessAborted(work, signal) {
|
|
3851
|
+
if (!signal) {
|
|
3852
|
+
return await work;
|
|
3853
|
+
}
|
|
3854
|
+
signal.throwIfAborted();
|
|
3855
|
+
return new Promise((resolve, reject) => {
|
|
3856
|
+
const onAbort = () => {
|
|
3857
|
+
cleanup();
|
|
3858
|
+
reject(createStepWalkAbortError());
|
|
3859
|
+
};
|
|
3860
|
+
const cleanup = () => {
|
|
3861
|
+
signal.removeEventListener('abort', onAbort);
|
|
3862
|
+
};
|
|
3863
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3864
|
+
Promise.resolve(work).then((value) => {
|
|
3865
|
+
cleanup();
|
|
3866
|
+
resolve(value);
|
|
3867
|
+
}, (error) => {
|
|
3868
|
+
cleanup();
|
|
3869
|
+
reject(error);
|
|
3870
|
+
});
|
|
3871
|
+
});
|
|
3872
|
+
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Result-aware runner for `gitAddCommitFn` / `gitPushFn`.
|
|
3875
|
+
* nullish Success → no-op; empty string / Failure / throw / exec fail → failed.
|
|
3876
|
+
*/
|
|
3877
|
+
async function runGitResultHook(input) {
|
|
3878
|
+
const { getResult, cwd, logger, execLabel } = input;
|
|
3771
3879
|
try {
|
|
3772
|
-
const
|
|
3773
|
-
if (command == null || command === '') {
|
|
3774
|
-
return 'ok';
|
|
3775
|
-
}
|
|
3776
|
-
const result = await execAsync(command, { cwd });
|
|
3777
|
-
logger.verbose(`${label} ${JSON.stringify(result)}`);
|
|
3880
|
+
const result = await getResult();
|
|
3778
3881
|
if (!result.success) {
|
|
3779
|
-
|
|
3780
|
-
|
|
3882
|
+
return { status: 'failed', detail: result.data };
|
|
3883
|
+
}
|
|
3884
|
+
const command = result.data;
|
|
3885
|
+
if (command == null) {
|
|
3886
|
+
return { status: 'ok' };
|
|
3887
|
+
}
|
|
3888
|
+
if (command === '') {
|
|
3889
|
+
return { status: 'failed', detail: 'empty command string' };
|
|
3781
3890
|
}
|
|
3782
|
-
|
|
3891
|
+
const execResult = await execAsync(command, { cwd });
|
|
3892
|
+
logger.verbose(`${execLabel} ${JSON.stringify(execResult)}`);
|
|
3893
|
+
if (!execResult.success) {
|
|
3894
|
+
return { status: 'failed', detail: execResult.data.message };
|
|
3895
|
+
}
|
|
3896
|
+
return { status: 'ok' };
|
|
3783
3897
|
}
|
|
3784
3898
|
catch (error) {
|
|
3785
3899
|
const message = error instanceof Error ? error.message : String(error);
|
|
3786
|
-
|
|
3787
|
-
return 'failed';
|
|
3900
|
+
return { status: 'failed', detail: message };
|
|
3788
3901
|
}
|
|
3789
3902
|
}
|
|
3790
|
-
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList,
|
|
3903
|
+
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommitFn, gitPushFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
|
|
3791
3904
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
3792
3905
|
const contextNames = contextList.map(context => context.name);
|
|
3793
3906
|
logger.verbose(`contextNames ${JSON.stringify(contextNames)}`);
|
|
@@ -3813,7 +3926,9 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3813
3926
|
});
|
|
3814
3927
|
logger.verbose(`setupWorkspaceCommandExec ${JSON.stringify(setupWorkspaceCommandExec)}`);
|
|
3815
3928
|
if (!setupWorkspaceCommandExec.success) {
|
|
3816
|
-
return
|
|
3929
|
+
return failure({
|
|
3930
|
+
message: `Failed to setup the workspace: ${setupWorkspaceCommandExec.data.message}`,
|
|
3931
|
+
});
|
|
3817
3932
|
}
|
|
3818
3933
|
if (afterExec) {
|
|
3819
3934
|
await afterExec({ workspacePath });
|
|
@@ -3825,6 +3940,10 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3825
3940
|
let runFailure;
|
|
3826
3941
|
try {
|
|
3827
3942
|
for (let i = 0; i < contextList.length; i++) {
|
|
3943
|
+
if (signal?.aborted) {
|
|
3944
|
+
runFailure = stepWalkAbortedFailure();
|
|
3945
|
+
break;
|
|
3946
|
+
}
|
|
3828
3947
|
const context = contextList[i];
|
|
3829
3948
|
logger.info(contextList.length > 1
|
|
3830
3949
|
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
@@ -3837,127 +3956,137 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3837
3956
|
const contextRunState = setupResult?.contextRunState || {};
|
|
3838
3957
|
let stepWalkFailure;
|
|
3839
3958
|
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
if (typeof step === 'function') {
|
|
3850
|
-
subSteps =
|
|
3959
|
+
try {
|
|
3960
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3961
|
+
if (stepWalkFailure) {
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
signal?.throwIfAborted();
|
|
3965
|
+
const step = stepsToExec[stepIndex];
|
|
3966
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3967
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3968
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3969
|
+
const subSteps = typeof step === 'function'
|
|
3970
|
+
? await awaitUnlessAborted(step({
|
|
3971
|
+
context,
|
|
3972
|
+
stepIndex: compositeStepIndex,
|
|
3973
|
+
contextRunState,
|
|
3974
|
+
lumpVariables,
|
|
3975
|
+
}), signal)
|
|
3976
|
+
: step;
|
|
3977
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3978
|
+
continue;
|
|
3979
|
+
}
|
|
3980
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3981
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3982
|
+
const prompt = promptFn
|
|
3983
|
+
? await awaitUnlessAborted(promptFn({
|
|
3851
3984
|
context,
|
|
3852
3985
|
stepIndex: compositeStepIndex,
|
|
3853
3986
|
contextRunState,
|
|
3854
3987
|
lumpVariables,
|
|
3988
|
+
stepVariables,
|
|
3989
|
+
}), signal)
|
|
3990
|
+
: '';
|
|
3991
|
+
const command = await awaitUnlessAborted(commandFn({
|
|
3992
|
+
context,
|
|
3993
|
+
prompt,
|
|
3994
|
+
stepIndex: compositeStepIndex,
|
|
3995
|
+
contextRunState,
|
|
3996
|
+
lumpVariables,
|
|
3997
|
+
stepVariables,
|
|
3998
|
+
projectRoot,
|
|
3999
|
+
workspacePath,
|
|
4000
|
+
}), signal);
|
|
4001
|
+
let commandResult = '';
|
|
4002
|
+
let commandSucceeded = true;
|
|
4003
|
+
if (command != null) {
|
|
4004
|
+
const { executable, args, env } = command;
|
|
4005
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
4006
|
+
if (env != null) {
|
|
4007
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
4008
|
+
}
|
|
4009
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
4010
|
+
const commandExec = await execBinary({
|
|
4011
|
+
binaryPath: executable,
|
|
4012
|
+
args,
|
|
4013
|
+
timeoutMillis,
|
|
4014
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
4015
|
+
cwd: workspacePath,
|
|
4016
|
+
signal,
|
|
4017
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3855
4018
|
});
|
|
4019
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
4020
|
+
if (!commandExec.success) {
|
|
4021
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
4022
|
+
if (aborted || !continueOnError) {
|
|
4023
|
+
stepWalkFailure = {
|
|
4024
|
+
success: false,
|
|
4025
|
+
data: {
|
|
4026
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
4027
|
+
reason: 'stepWalkFailed',
|
|
4028
|
+
},
|
|
4029
|
+
};
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
commandSucceeded = false;
|
|
4033
|
+
commandResult = (commandExec.data.stdout
|
|
4034
|
+
|| commandExec.data.stderr
|
|
4035
|
+
|| commandExec.data.message
|
|
4036
|
+
|| '').toString();
|
|
4037
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
4038
|
+
}
|
|
4039
|
+
else {
|
|
4040
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
4041
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
4042
|
+
}
|
|
4043
|
+
if (commandSucceeded) {
|
|
4044
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
4045
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
4046
|
+
}
|
|
3856
4047
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3861
|
-
continue;
|
|
3862
|
-
}
|
|
3863
|
-
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3864
|
-
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3865
|
-
const prompt = promptFn
|
|
3866
|
-
? await promptFn({
|
|
4048
|
+
const historyEntry = {
|
|
4049
|
+
commandResult,
|
|
4050
|
+
commandSucceeded,
|
|
3867
4051
|
context,
|
|
4052
|
+
prompt,
|
|
3868
4053
|
stepIndex: compositeStepIndex,
|
|
3869
4054
|
contextRunState,
|
|
3870
4055
|
lumpVariables,
|
|
3871
4056
|
stepVariables,
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
prompt,
|
|
3877
|
-
stepIndex: compositeStepIndex,
|
|
3878
|
-
contextRunState,
|
|
3879
|
-
lumpVariables,
|
|
3880
|
-
stepVariables,
|
|
3881
|
-
projectRoot,
|
|
3882
|
-
workspacePath,
|
|
3883
|
-
});
|
|
3884
|
-
let commandResult = '';
|
|
3885
|
-
let commandSucceeded = true;
|
|
3886
|
-
if (command != null) {
|
|
3887
|
-
const { executable, args, env } = command;
|
|
3888
|
-
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3889
|
-
if (env != null) {
|
|
3890
|
-
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3891
|
-
}
|
|
3892
|
-
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3893
|
-
const commandExec = await execBinary({
|
|
3894
|
-
binaryPath: executable,
|
|
3895
|
-
args,
|
|
3896
|
-
timeoutMillis,
|
|
3897
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3898
|
-
cwd: workspacePath,
|
|
4057
|
+
projectRoot,
|
|
4058
|
+
};
|
|
4059
|
+
const postCommandExecFnInput = {
|
|
4060
|
+
...historyEntry,
|
|
3899
4061
|
signal,
|
|
3900
|
-
|
|
3901
|
-
});
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
};
|
|
3913
|
-
return;
|
|
4062
|
+
};
|
|
4063
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
4064
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
4065
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
4066
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
4067
|
+
const appendResult = await appendHistoryEntry({
|
|
4068
|
+
filePath: keepHistoryFilePath,
|
|
4069
|
+
entry: historyEntry,
|
|
4070
|
+
});
|
|
4071
|
+
if (!appendResult.success) {
|
|
4072
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
4073
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3914
4074
|
}
|
|
3915
|
-
commandSucceeded = false;
|
|
3916
|
-
commandResult = (commandExec.data.stdout
|
|
3917
|
-
|| commandExec.data.stderr
|
|
3918
|
-
|| commandExec.data.message
|
|
3919
|
-
|| '').toString();
|
|
3920
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3921
|
-
}
|
|
3922
|
-
else {
|
|
3923
|
-
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3924
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3925
|
-
}
|
|
3926
|
-
if (commandSucceeded) {
|
|
3927
|
-
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3928
|
-
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3929
4075
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
prompt,
|
|
3936
|
-
stepIndex: compositeStepIndex,
|
|
3937
|
-
contextRunState,
|
|
3938
|
-
lumpVariables,
|
|
3939
|
-
stepVariables,
|
|
3940
|
-
projectRoot,
|
|
3941
|
-
};
|
|
3942
|
-
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3943
|
-
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3944
|
-
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3945
|
-
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3946
|
-
const appendResult = await appendHistoryEntry({
|
|
3947
|
-
filePath: keepHistoryFilePath,
|
|
3948
|
-
entry: postCommandExecFnInput,
|
|
3949
|
-
});
|
|
3950
|
-
if (!appendResult.success) {
|
|
3951
|
-
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3952
|
-
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
4076
|
+
if (postCommandExecFn) {
|
|
4077
|
+
const returnedSteps = await awaitUnlessAborted(postCommandExecFn(postCommandExecFnInput), signal);
|
|
4078
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
4079
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
4080
|
+
}
|
|
3953
4081
|
}
|
|
3954
4082
|
}
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4083
|
+
}
|
|
4084
|
+
catch (error) {
|
|
4085
|
+
if (isStepWalkAbortError(error)) {
|
|
4086
|
+
stepWalkFailure = stepWalkAbortedFailure();
|
|
4087
|
+
return;
|
|
3960
4088
|
}
|
|
4089
|
+
throw error;
|
|
3961
4090
|
}
|
|
3962
4091
|
}
|
|
3963
4092
|
try {
|
|
@@ -3981,43 +4110,42 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3981
4110
|
runFailure = stepWalkFailure;
|
|
3982
4111
|
break;
|
|
3983
4112
|
}
|
|
3984
|
-
const
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
4113
|
+
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
4114
|
+
const addCommitExecLabel = `git add+commit for context ${context.name}`;
|
|
4115
|
+
const addCommitOutcome = await runGitResultHook({
|
|
4116
|
+
getResult: () => gitAddCommitFn({
|
|
4117
|
+
...injectedGitAndWorkspaceFnsInput,
|
|
4118
|
+
context,
|
|
4119
|
+
commitMessage,
|
|
4120
|
+
}),
|
|
3991
4121
|
cwd: workspacePath,
|
|
3992
4122
|
logger,
|
|
4123
|
+
execLabel: addCommitExecLabel,
|
|
3993
4124
|
});
|
|
3994
|
-
if (
|
|
4125
|
+
if (addCommitOutcome.status === 'failed') {
|
|
4126
|
+
const message = `Failed to add and commit for context ${context.name}: ${addCommitOutcome.detail}`;
|
|
4127
|
+
logger.error(message);
|
|
3995
4128
|
runFailure = {
|
|
3996
4129
|
success: false,
|
|
3997
4130
|
data: {
|
|
3998
|
-
message
|
|
4131
|
+
message,
|
|
4132
|
+
reason: 'gitAddCommitFailed',
|
|
3999
4133
|
},
|
|
4000
4134
|
};
|
|
4001
4135
|
break;
|
|
4002
4136
|
}
|
|
4003
|
-
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
4004
|
-
await runOptionalGitCommand({
|
|
4005
|
-
label: `git commit for context ${context.name}`,
|
|
4006
|
-
getCommand: () => gitCommitCommandFn({
|
|
4007
|
-
...perContextInput,
|
|
4008
|
-
commitMessage,
|
|
4009
|
-
}),
|
|
4010
|
-
cwd: workspacePath,
|
|
4011
|
-
logger,
|
|
4012
|
-
});
|
|
4013
4137
|
}
|
|
4014
4138
|
if (!runFailure) {
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4139
|
+
const pushExecLabel = `git push on branch ${branchName}`;
|
|
4140
|
+
const pushOutcome = await runGitResultHook({
|
|
4141
|
+
getResult: () => gitPushFn(injectedGitAndWorkspaceFnsInput),
|
|
4018
4142
|
cwd: workspacePath,
|
|
4019
4143
|
logger,
|
|
4144
|
+
execLabel: pushExecLabel,
|
|
4020
4145
|
});
|
|
4146
|
+
if (pushOutcome.status === 'failed') {
|
|
4147
|
+
logger.error(`Failed to ${pushExecLabel}: ${pushOutcome.detail}`);
|
|
4148
|
+
}
|
|
4021
4149
|
}
|
|
4022
4150
|
}
|
|
4023
4151
|
finally {
|
|
@@ -4159,13 +4287,13 @@ async function getContextStatus(params) {
|
|
|
4159
4287
|
if (!fetchResult.success)
|
|
4160
4288
|
return 'toDo';
|
|
4161
4289
|
}
|
|
4162
|
-
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(
|
|
4290
|
+
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(GIT_LOG_HASH_BODY_FORMAT)}`, { cwd: projectRoot });
|
|
4163
4291
|
if (!logResult.success)
|
|
4164
4292
|
return 'toDo';
|
|
4165
4293
|
logger?.verbose(`logResult ${JSON.stringify(logResult.data)}`);
|
|
4166
4294
|
const logResultOutput = logResult.data.stdout || logResult.data.stderr || '';
|
|
4167
|
-
const matchingHashes =
|
|
4168
|
-
.filter((entry) => entry.
|
|
4295
|
+
const matchingHashes = parseGitLogHashBodyRecords(logResultOutput)
|
|
4296
|
+
.filter((entry) => commitMessageIncludesMarker(entry.message, commitMessage))
|
|
4169
4297
|
.map((entry) => entry.hash);
|
|
4170
4298
|
logger?.verbose(`contextName ${contextName}`);
|
|
4171
4299
|
logger?.verbose(`remoteName ${remoteName}`);
|
|
@@ -4282,14 +4410,11 @@ const contextStatus = ['toDo', 'branchPushed', 'finished'];
|
|
|
4282
4410
|
const defaultGitCommitMessageFn = ({ context }) => {
|
|
4283
4411
|
return `LUMP:${context.name}`;
|
|
4284
4412
|
};
|
|
4285
|
-
const
|
|
4286
|
-
return `git
|
|
4287
|
-
};
|
|
4288
|
-
const defaultGitAddCommandFn = () => {
|
|
4289
|
-
return `git add .`;
|
|
4413
|
+
const defaultGitAddCommitFn = (input) => {
|
|
4414
|
+
return success(`git add . && git commit --allow-empty -m ${shellSingleQuote(input.commitMessage)}`);
|
|
4290
4415
|
};
|
|
4291
|
-
const
|
|
4292
|
-
return `git
|
|
4416
|
+
const defaultGitPushFn = (input) => {
|
|
4417
|
+
return success(`git push origin ${shellSingleQuote(input.branchName)}`);
|
|
4293
4418
|
};
|
|
4294
4419
|
const defaultSetupWorkspaceFn = async (input) => {
|
|
4295
4420
|
const { baseBranch, branchName } = input;
|
|
@@ -4327,7 +4452,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
|
|
|
4327
4452
|
};
|
|
4328
4453
|
|
|
4329
4454
|
async function runLump(input) {
|
|
4330
|
-
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn,
|
|
4455
|
+
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn, gitAddCommitFn = defaultGitAddCommitFn, gitCommitMessageFn = defaultGitCommitMessageFn, gitPushFn = defaultGitPushFn, numberOfContextsPerBranch = 1, projectRoot, steps, setupFn = () => ({ contextRunState: {} }), setupWorkspaceFn = defaultSetupWorkspaceFn, teardownFn = () => undefined, teardownWorkspaceFn = defaultTeardownWorkspaceFn, getKeepHistoryFilePathFn = () => undefined, logger: loggerInput, signal, refreshRemoteTrackingRefsFn, } = input;
|
|
4331
4456
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4332
4457
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
4333
4458
|
const contextListToDoResult = await getToDoContextList({
|
|
@@ -4362,10 +4487,9 @@ async function runLump(input) {
|
|
|
4362
4487
|
branchFn: branchFn,
|
|
4363
4488
|
lumpVariables: lumpVariables,
|
|
4364
4489
|
contextList: nextContextsForBranchList,
|
|
4365
|
-
|
|
4366
|
-
gitCommitCommandFn,
|
|
4490
|
+
gitAddCommitFn,
|
|
4367
4491
|
gitCommitMessageFn,
|
|
4368
|
-
|
|
4492
|
+
gitPushFn,
|
|
4369
4493
|
projectRoot,
|
|
4370
4494
|
steps,
|
|
4371
4495
|
setupFn,
|
|
@@ -4384,14 +4508,15 @@ async function runLump(input) {
|
|
|
4384
4508
|
});
|
|
4385
4509
|
}
|
|
4386
4510
|
|
|
4511
|
+
exports.GIT_LOG_HASH_BODY_FORMAT = GIT_LOG_HASH_BODY_FORMAT;
|
|
4387
4512
|
exports.appendHistoryEntry = appendHistoryEntry;
|
|
4388
4513
|
exports.collectStepsForContext = collectStepsForContext;
|
|
4514
|
+
exports.commitMessageIncludesMarker = commitMessageIncludesMarker;
|
|
4389
4515
|
exports.contextStatus = contextStatus;
|
|
4390
4516
|
exports.createConsoleLogger = createConsoleLogger;
|
|
4391
|
-
exports.
|
|
4392
|
-
exports.defaultGitCommitCommandFn = defaultGitCommitCommandFn;
|
|
4517
|
+
exports.defaultGitAddCommitFn = defaultGitAddCommitFn;
|
|
4393
4518
|
exports.defaultGitCommitMessageFn = defaultGitCommitMessageFn;
|
|
4394
|
-
exports.
|
|
4519
|
+
exports.defaultGitPushFn = defaultGitPushFn;
|
|
4395
4520
|
exports.defaultSetupWorkspaceFn = defaultSetupWorkspaceFn;
|
|
4396
4521
|
exports.defaultSetupWorkspaceFnWithWorktree = defaultSetupWorkspaceFnWithWorktree;
|
|
4397
4522
|
exports.defaultTeardownWorkspaceFn = defaultTeardownWorkspaceFn;
|
|
@@ -4408,6 +4533,7 @@ exports.historyFormatFromPath = historyFormatFromPath;
|
|
|
4408
4533
|
exports.isProcessAlive = isProcessAlive;
|
|
4409
4534
|
exports.killProcessTree = killProcessTree;
|
|
4410
4535
|
exports.nodeErrnoCode = nodeErrnoCode;
|
|
4536
|
+
exports.parseGitLogHashBodyRecords = parseGitLogHashBodyRecords;
|
|
4411
4537
|
exports.parseGitLogHashSubjectLines = parseGitLogHashSubjectLines;
|
|
4412
4538
|
exports.pathExists = pathExists;
|
|
4413
4539
|
exports.readHistoryFile = readHistoryFile;
|