@lumpcode/core 0.0.16 → 0.1.1
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 +3 -3
- package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
- package/dist/helpers/getContextStatus/main.d.ts +5 -0
- package/dist/helpers/getContextStatus/main.d.ts.map +1 -1
- package/dist/helpers/getToDoContextList/main.d.ts +6 -0
- package/dist/helpers/getToDoContextList/main.d.ts.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts +2 -0
- package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts.map +1 -0
- package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts +11 -0
- package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts.map +1 -0
- package/dist/index.cjs +289 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +286 -145
- package/dist/index.js.map +1 -1
- package/dist/types/ExecShellFn.d.ts +16 -0
- package/dist/types/ExecShellFn.d.ts.map +1 -0
- package/dist/types/GitAddCommandFn.d.ts +7 -1
- package/dist/types/GitAddCommandFn.d.ts.map +1 -1
- package/dist/types/GitCommitCommandFn.d.ts +7 -1
- package/dist/types/GitCommitCommandFn.d.ts.map +1 -1
- package/dist/types/GitPushCommandFn.d.ts +7 -1
- package/dist/types/GitPushCommandFn.d.ts.map +1 -1
- package/dist/types/PostCommandExecFn.d.ts +2 -0
- package/dist/types/PostCommandExecFn.d.ts.map +1 -1
- package/dist/usages/runLump/main.d.ts +6 -1
- 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) {
|
|
@@ -3766,6 +3800,75 @@ async function collectStepsForContext(params) {
|
|
|
3766
3800
|
return collectedSteps;
|
|
3767
3801
|
}
|
|
3768
3802
|
|
|
3803
|
+
const ABORT_STEP_WALK_MESSAGE = 'Process aborted';
|
|
3804
|
+
function stepWalkAbortedFailure() {
|
|
3805
|
+
return {
|
|
3806
|
+
success: false,
|
|
3807
|
+
data: {
|
|
3808
|
+
message: ABORT_STEP_WALK_MESSAGE,
|
|
3809
|
+
reason: 'stepWalkFailed',
|
|
3810
|
+
},
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
function createStepWalkAbortError() {
|
|
3814
|
+
const error = new Error(ABORT_STEP_WALK_MESSAGE);
|
|
3815
|
+
error.name = 'AbortError';
|
|
3816
|
+
return error;
|
|
3817
|
+
}
|
|
3818
|
+
function isStepWalkAbortError(error) {
|
|
3819
|
+
return (typeof error === 'object'
|
|
3820
|
+
&& error !== null
|
|
3821
|
+
&& 'name' in error
|
|
3822
|
+
&& error.name === 'AbortError');
|
|
3823
|
+
}
|
|
3824
|
+
/**
|
|
3825
|
+
* Await user-hook work, but stop waiting once `signal` aborts so the walk can
|
|
3826
|
+
* unwind (locks/teardown). Does not cancel sync busy-loops on the event loop.
|
|
3827
|
+
*/
|
|
3828
|
+
async function awaitUnlessAborted(work, signal) {
|
|
3829
|
+
if (!signal) {
|
|
3830
|
+
return await work;
|
|
3831
|
+
}
|
|
3832
|
+
signal.throwIfAborted();
|
|
3833
|
+
return new Promise((resolve, reject) => {
|
|
3834
|
+
const onAbort = () => {
|
|
3835
|
+
cleanup();
|
|
3836
|
+
reject(createStepWalkAbortError());
|
|
3837
|
+
};
|
|
3838
|
+
const cleanup = () => {
|
|
3839
|
+
signal.removeEventListener('abort', onAbort);
|
|
3840
|
+
};
|
|
3841
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3842
|
+
Promise.resolve(work).then((value) => {
|
|
3843
|
+
cleanup();
|
|
3844
|
+
resolve(value);
|
|
3845
|
+
}, (error) => {
|
|
3846
|
+
cleanup();
|
|
3847
|
+
reject(error);
|
|
3848
|
+
});
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
async function runOptionalGitCommand(input) {
|
|
3852
|
+
const { label, getCommand, cwd, logger } = input;
|
|
3853
|
+
try {
|
|
3854
|
+
const command = await getCommand();
|
|
3855
|
+
if (command == null || command === '') {
|
|
3856
|
+
return 'ok';
|
|
3857
|
+
}
|
|
3858
|
+
const result = await execAsync(command, { cwd });
|
|
3859
|
+
logger.verbose(`${label} ${JSON.stringify(result)}`);
|
|
3860
|
+
if (!result.success) {
|
|
3861
|
+
logger.error(formatExecFailureMessage({ label, failure: result }));
|
|
3862
|
+
return 'failed';
|
|
3863
|
+
}
|
|
3864
|
+
return 'ok';
|
|
3865
|
+
}
|
|
3866
|
+
catch (error) {
|
|
3867
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3868
|
+
logger.error(`Failed to run ${label}: ${message}`);
|
|
3869
|
+
return 'failed';
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3769
3872
|
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
|
|
3770
3873
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
3771
3874
|
const contextNames = contextList.map(context => context.name);
|
|
@@ -3804,6 +3907,10 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3804
3907
|
let runFailure;
|
|
3805
3908
|
try {
|
|
3806
3909
|
for (let i = 0; i < contextList.length; i++) {
|
|
3910
|
+
if (signal?.aborted) {
|
|
3911
|
+
runFailure = stepWalkAbortedFailure();
|
|
3912
|
+
break;
|
|
3913
|
+
}
|
|
3807
3914
|
const context = contextList[i];
|
|
3808
3915
|
logger.info(contextList.length > 1
|
|
3809
3916
|
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
@@ -3816,127 +3923,137 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3816
3923
|
const contextRunState = setupResult?.contextRunState || {};
|
|
3817
3924
|
let stepWalkFailure;
|
|
3818
3925
|
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
if (typeof step === 'function') {
|
|
3829
|
-
subSteps =
|
|
3926
|
+
try {
|
|
3927
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3928
|
+
if (stepWalkFailure) {
|
|
3929
|
+
return;
|
|
3930
|
+
}
|
|
3931
|
+
signal?.throwIfAborted();
|
|
3932
|
+
const step = stepsToExec[stepIndex];
|
|
3933
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3934
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3935
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3936
|
+
const subSteps = typeof step === 'function'
|
|
3937
|
+
? await awaitUnlessAborted(step({
|
|
3938
|
+
context,
|
|
3939
|
+
stepIndex: compositeStepIndex,
|
|
3940
|
+
contextRunState,
|
|
3941
|
+
lumpVariables,
|
|
3942
|
+
}), signal)
|
|
3943
|
+
: step;
|
|
3944
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3945
|
+
continue;
|
|
3946
|
+
}
|
|
3947
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3948
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3949
|
+
const prompt = promptFn
|
|
3950
|
+
? await awaitUnlessAborted(promptFn({
|
|
3830
3951
|
context,
|
|
3831
3952
|
stepIndex: compositeStepIndex,
|
|
3832
3953
|
contextRunState,
|
|
3833
3954
|
lumpVariables,
|
|
3955
|
+
stepVariables,
|
|
3956
|
+
}), signal)
|
|
3957
|
+
: '';
|
|
3958
|
+
const command = await awaitUnlessAborted(commandFn({
|
|
3959
|
+
context,
|
|
3960
|
+
prompt,
|
|
3961
|
+
stepIndex: compositeStepIndex,
|
|
3962
|
+
contextRunState,
|
|
3963
|
+
lumpVariables,
|
|
3964
|
+
stepVariables,
|
|
3965
|
+
projectRoot,
|
|
3966
|
+
workspacePath,
|
|
3967
|
+
}), signal);
|
|
3968
|
+
let commandResult = '';
|
|
3969
|
+
let commandSucceeded = true;
|
|
3970
|
+
if (command != null) {
|
|
3971
|
+
const { executable, args, env } = command;
|
|
3972
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3973
|
+
if (env != null) {
|
|
3974
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3975
|
+
}
|
|
3976
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3977
|
+
const commandExec = await execBinary({
|
|
3978
|
+
binaryPath: executable,
|
|
3979
|
+
args,
|
|
3980
|
+
timeoutMillis,
|
|
3981
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3982
|
+
cwd: workspacePath,
|
|
3983
|
+
signal,
|
|
3984
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3834
3985
|
});
|
|
3986
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
3987
|
+
if (!commandExec.success) {
|
|
3988
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
3989
|
+
if (aborted || !continueOnError) {
|
|
3990
|
+
stepWalkFailure = {
|
|
3991
|
+
success: false,
|
|
3992
|
+
data: {
|
|
3993
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
3994
|
+
reason: 'stepWalkFailed',
|
|
3995
|
+
},
|
|
3996
|
+
};
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
commandSucceeded = false;
|
|
4000
|
+
commandResult = (commandExec.data.stdout
|
|
4001
|
+
|| commandExec.data.stderr
|
|
4002
|
+
|| commandExec.data.message
|
|
4003
|
+
|| '').toString();
|
|
4004
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
4005
|
+
}
|
|
4006
|
+
else {
|
|
4007
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
4008
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
4009
|
+
}
|
|
4010
|
+
if (commandSucceeded) {
|
|
4011
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
4012
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
4013
|
+
}
|
|
3835
4014
|
}
|
|
3836
|
-
|
|
3837
|
-
|
|
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({
|
|
4015
|
+
const historyEntry = {
|
|
4016
|
+
commandResult,
|
|
4017
|
+
commandSucceeded,
|
|
3846
4018
|
context,
|
|
4019
|
+
prompt,
|
|
3847
4020
|
stepIndex: compositeStepIndex,
|
|
3848
4021
|
contextRunState,
|
|
3849
4022
|
lumpVariables,
|
|
3850
4023
|
stepVariables,
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
prompt,
|
|
3856
|
-
stepIndex: compositeStepIndex,
|
|
3857
|
-
contextRunState,
|
|
3858
|
-
lumpVariables,
|
|
3859
|
-
stepVariables,
|
|
3860
|
-
projectRoot,
|
|
3861
|
-
workspacePath,
|
|
3862
|
-
});
|
|
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,
|
|
4024
|
+
projectRoot,
|
|
4025
|
+
};
|
|
4026
|
+
const postCommandExecFnInput = {
|
|
4027
|
+
...historyEntry,
|
|
3878
4028
|
signal,
|
|
3879
|
-
|
|
3880
|
-
});
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
};
|
|
3892
|
-
return;
|
|
4029
|
+
};
|
|
4030
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
4031
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
4032
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
4033
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
4034
|
+
const appendResult = await appendHistoryEntry({
|
|
4035
|
+
filePath: keepHistoryFilePath,
|
|
4036
|
+
entry: historyEntry,
|
|
4037
|
+
});
|
|
4038
|
+
if (!appendResult.success) {
|
|
4039
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
4040
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3893
4041
|
}
|
|
3894
|
-
commandSucceeded = false;
|
|
3895
|
-
commandResult = (commandExec.data.stdout
|
|
3896
|
-
|| commandExec.data.stderr
|
|
3897
|
-
|| commandExec.data.message
|
|
3898
|
-
|| '').toString();
|
|
3899
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3900
4042
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3907
|
-
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
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}`);
|
|
4043
|
+
if (postCommandExecFn) {
|
|
4044
|
+
const returnedSteps = await awaitUnlessAborted(postCommandExecFn(postCommandExecFnInput), signal);
|
|
4045
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
4046
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
4047
|
+
}
|
|
3932
4048
|
}
|
|
3933
4049
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
4050
|
+
}
|
|
4051
|
+
catch (error) {
|
|
4052
|
+
if (isStepWalkAbortError(error)) {
|
|
4053
|
+
stepWalkFailure = stepWalkAbortedFailure();
|
|
4054
|
+
return;
|
|
3939
4055
|
}
|
|
4056
|
+
throw error;
|
|
3940
4057
|
}
|
|
3941
4058
|
}
|
|
3942
4059
|
try {
|
|
@@ -3964,39 +4081,39 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3964
4081
|
...injectedGitAndWorkspaceFnsInput,
|
|
3965
4082
|
context,
|
|
3966
4083
|
};
|
|
3967
|
-
const
|
|
3968
|
-
|
|
3969
|
-
|
|
4084
|
+
const addOutcome = await runOptionalGitCommand({
|
|
4085
|
+
label: `git add for context ${context.name}`,
|
|
4086
|
+
getCommand: () => gitAddCommandFn(perContextInput),
|
|
4087
|
+
cwd: workspacePath,
|
|
4088
|
+
logger,
|
|
4089
|
+
});
|
|
4090
|
+
if (addOutcome === 'failed') {
|
|
3970
4091
|
runFailure = {
|
|
3971
4092
|
success: false,
|
|
3972
4093
|
data: {
|
|
3973
|
-
message: `Failed to add the changes for context ${context.name}
|
|
4094
|
+
message: `Failed to add the changes for context ${context.name}`,
|
|
3974
4095
|
},
|
|
3975
4096
|
};
|
|
3976
4097
|
break;
|
|
3977
4098
|
}
|
|
3978
4099
|
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
}));
|
|
3989
|
-
}
|
|
4100
|
+
await runOptionalGitCommand({
|
|
4101
|
+
label: `git commit for context ${context.name}`,
|
|
4102
|
+
getCommand: () => gitCommitCommandFn({
|
|
4103
|
+
...perContextInput,
|
|
4104
|
+
commitMessage,
|
|
4105
|
+
}),
|
|
4106
|
+
cwd: workspacePath,
|
|
4107
|
+
logger,
|
|
4108
|
+
});
|
|
3990
4109
|
}
|
|
3991
4110
|
if (!runFailure) {
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
}));
|
|
3999
|
-
}
|
|
4111
|
+
await runOptionalGitCommand({
|
|
4112
|
+
label: `git push on branch ${branchName}`,
|
|
4113
|
+
getCommand: () => gitPushCommandFn(injectedGitAndWorkspaceFnsInput),
|
|
4114
|
+
cwd: workspacePath,
|
|
4115
|
+
logger,
|
|
4116
|
+
});
|
|
4000
4117
|
}
|
|
4001
4118
|
}
|
|
4002
4119
|
finally {
|
|
@@ -4112,25 +4229,41 @@ async function scanDirectory({ dirPath, allPaths, gitignoresMap, projectRoot, lo
|
|
|
4112
4229
|
}
|
|
4113
4230
|
}
|
|
4114
4231
|
|
|
4232
|
+
/**
|
|
4233
|
+
* Refreshes remote-tracking refs for status / planning (one network fetch).
|
|
4234
|
+
* Uses `--no-write-fetch-head` so concurrent preflight/pull paths are safer.
|
|
4235
|
+
*/
|
|
4236
|
+
const refreshRemoteTrackingRefs = async (input) => {
|
|
4237
|
+
const { projectRoot, remoteName = 'origin' } = input;
|
|
4238
|
+
const result = await execAsync(`git fetch --prune --no-write-fetch-head ${remoteName}`, { cwd: projectRoot });
|
|
4239
|
+
if (!result.success) {
|
|
4240
|
+
return failure(result.data.message);
|
|
4241
|
+
}
|
|
4242
|
+
return success(undefined);
|
|
4243
|
+
};
|
|
4244
|
+
|
|
4115
4245
|
async function getContextStatus(params) {
|
|
4116
|
-
const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, } = params;
|
|
4246
|
+
const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, skipFetch = false, } = params;
|
|
4117
4247
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4118
4248
|
const commitMessage = gitCommitMessageFn({
|
|
4119
4249
|
context: { name: contextName, variables: contextVariables },
|
|
4120
4250
|
lumpVariables,
|
|
4121
4251
|
baseBranch,
|
|
4122
4252
|
});
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4253
|
+
if (!skipFetch) {
|
|
4254
|
+
const fetchResult = await refreshRemoteTrackingRefs({ projectRoot, remoteName });
|
|
4255
|
+
if (!fetchResult.success)
|
|
4256
|
+
return 'toDo';
|
|
4257
|
+
}
|
|
4258
|
+
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(GIT_LOG_HASH_BODY_FORMAT)}`, { cwd: projectRoot });
|
|
4127
4259
|
if (!logResult.success)
|
|
4128
4260
|
return 'toDo';
|
|
4129
4261
|
logger?.verbose(`logResult ${JSON.stringify(logResult.data)}`);
|
|
4130
4262
|
const logResultOutput = logResult.data.stdout || logResult.data.stderr || '';
|
|
4131
|
-
const matchingHashes =
|
|
4132
|
-
.filter((entry) => entry.
|
|
4263
|
+
const matchingHashes = parseGitLogHashBodyRecords(logResultOutput)
|
|
4264
|
+
.filter((entry) => commitMessageIncludesMarker(entry.message, commitMessage))
|
|
4133
4265
|
.map((entry) => entry.hash);
|
|
4266
|
+
logger?.verbose(`contextName ${contextName}`);
|
|
4134
4267
|
logger?.verbose(`remoteName ${remoteName}`);
|
|
4135
4268
|
logger?.verbose(`baseBranch ${baseBranch}`);
|
|
4136
4269
|
logger?.verbose(`commitMessage ${commitMessage}`);
|
|
@@ -4185,7 +4318,7 @@ function validateContextListNames(contextList) {
|
|
|
4185
4318
|
}
|
|
4186
4319
|
|
|
4187
4320
|
async function getToDoContextList(params) {
|
|
4188
|
-
const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger } = params;
|
|
4321
|
+
const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger, refreshRemoteTrackingRefsFn = refreshRemoteTrackingRefs, } = params;
|
|
4189
4322
|
const codeBasePathsResult = await getCodeBasePaths({ cwd: projectRoot, logger });
|
|
4190
4323
|
if (!codeBasePathsResult.success) {
|
|
4191
4324
|
return failure({
|
|
@@ -4203,21 +4336,28 @@ async function getToDoContextList(params) {
|
|
|
4203
4336
|
const allCtxNames = contextList.flatMap(context => [context.name, ...(context.options?.dependsOnContexts ?? [])]);
|
|
4204
4337
|
const allCtxNamesSet = new Set(allCtxNames);
|
|
4205
4338
|
const allCtxNamesList = Array.from(allCtxNamesSet);
|
|
4206
|
-
const
|
|
4207
|
-
|
|
4208
|
-
|
|
4339
|
+
const refreshResult = await refreshRemoteTrackingRefsFn({ projectRoot });
|
|
4340
|
+
let contextStatusMap;
|
|
4341
|
+
if (!refreshResult.success) {
|
|
4342
|
+
logger?.warn(`Failed to refresh remote-tracking refs for context status; treating contexts as toDo: ${refreshResult.data}`);
|
|
4343
|
+
contextStatusMap = new Map(allCtxNamesList.map((name) => [name, 'toDo']));
|
|
4344
|
+
}
|
|
4345
|
+
else {
|
|
4346
|
+
const contextStatusList = await Promise.all(allCtxNamesList.map((contextName) => getContextStatus({
|
|
4347
|
+
contextName,
|
|
4209
4348
|
contextVariables: {}, // TODO: Remove contextVariables from getContextStatus, really not needed
|
|
4210
4349
|
gitCommitMessageFn,
|
|
4211
4350
|
lumpVariables,
|
|
4212
4351
|
projectRoot,
|
|
4213
4352
|
baseBranch,
|
|
4214
4353
|
logger,
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4354
|
+
skipFetch: true,
|
|
4355
|
+
})));
|
|
4356
|
+
contextStatusMap = new Map(allCtxNamesList.map((contextName, i) => [contextName, contextStatusList[i]]));
|
|
4357
|
+
}
|
|
4218
4358
|
const contextListToDo = contextList
|
|
4219
|
-
.filter((context
|
|
4220
|
-
const contextStatus =
|
|
4359
|
+
.filter((context) => {
|
|
4360
|
+
const contextStatus = contextStatusMap.get(context.name);
|
|
4221
4361
|
if (contextStatus && contextStatus !== 'toDo')
|
|
4222
4362
|
return false;
|
|
4223
4363
|
const deps = context.options?.dependsOnContexts;
|
|
@@ -4283,7 +4423,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
|
|
|
4283
4423
|
};
|
|
4284
4424
|
|
|
4285
4425
|
async function runLump(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;
|
|
4426
|
+
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, refreshRemoteTrackingRefsFn, } = input;
|
|
4287
4427
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4288
4428
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
4289
4429
|
const contextListToDoResult = await getToDoContextList({
|
|
@@ -4293,6 +4433,7 @@ async function runLump(input) {
|
|
|
4293
4433
|
baseBranch,
|
|
4294
4434
|
gitCommitMessageFn,
|
|
4295
4435
|
logger,
|
|
4436
|
+
refreshRemoteTrackingRefsFn,
|
|
4296
4437
|
});
|
|
4297
4438
|
if (!contextListToDoResult.success) {
|
|
4298
4439
|
return set(contextListToDoResult, ['data', 'message'], "Error in runLump: Failed to get to do context list. Original Error: " + contextListToDoResult.data.message);
|
|
@@ -4339,8 +4480,10 @@ async function runLump(input) {
|
|
|
4339
4480
|
});
|
|
4340
4481
|
}
|
|
4341
4482
|
|
|
4483
|
+
exports.GIT_LOG_HASH_BODY_FORMAT = GIT_LOG_HASH_BODY_FORMAT;
|
|
4342
4484
|
exports.appendHistoryEntry = appendHistoryEntry;
|
|
4343
4485
|
exports.collectStepsForContext = collectStepsForContext;
|
|
4486
|
+
exports.commitMessageIncludesMarker = commitMessageIncludesMarker;
|
|
4344
4487
|
exports.contextStatus = contextStatus;
|
|
4345
4488
|
exports.createConsoleLogger = createConsoleLogger;
|
|
4346
4489
|
exports.defaultGitAddCommandFn = defaultGitAddCommandFn;
|
|
@@ -4363,9 +4506,11 @@ exports.historyFormatFromPath = historyFormatFromPath;
|
|
|
4363
4506
|
exports.isProcessAlive = isProcessAlive;
|
|
4364
4507
|
exports.killProcessTree = killProcessTree;
|
|
4365
4508
|
exports.nodeErrnoCode = nodeErrnoCode;
|
|
4509
|
+
exports.parseGitLogHashBodyRecords = parseGitLogHashBodyRecords;
|
|
4366
4510
|
exports.parseGitLogHashSubjectLines = parseGitLogHashSubjectLines;
|
|
4367
4511
|
exports.pathExists = pathExists;
|
|
4368
4512
|
exports.readHistoryFile = readHistoryFile;
|
|
4513
|
+
exports.refreshRemoteTrackingRefs = refreshRemoteTrackingRefs;
|
|
4369
4514
|
exports.resolveSpawnExecutable = resolveSpawnExecutable;
|
|
4370
4515
|
exports.runLump = runLump;
|
|
4371
4516
|
exports.set = set;
|