@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.
Files changed (39) hide show
  1. package/README.md +3 -3
  2. package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
  3. package/dist/helpers/getContextStatus/main.d.ts +5 -0
  4. package/dist/helpers/getContextStatus/main.d.ts.map +1 -1
  5. package/dist/helpers/getToDoContextList/main.d.ts +6 -0
  6. package/dist/helpers/getToDoContextList/main.d.ts.map +1 -1
  7. package/dist/helpers/index.d.ts +1 -0
  8. package/dist/helpers/index.d.ts.map +1 -1
  9. package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts +2 -0
  10. package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts.map +1 -0
  11. package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts +11 -0
  12. package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts.map +1 -0
  13. package/dist/index.cjs +289 -144
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +286 -145
  16. package/dist/index.js.map +1 -1
  17. package/dist/types/ExecShellFn.d.ts +16 -0
  18. package/dist/types/ExecShellFn.d.ts.map +1 -0
  19. package/dist/types/GitAddCommandFn.d.ts +7 -1
  20. package/dist/types/GitAddCommandFn.d.ts.map +1 -1
  21. package/dist/types/GitCommitCommandFn.d.ts +7 -1
  22. package/dist/types/GitCommitCommandFn.d.ts.map +1 -1
  23. package/dist/types/GitPushCommandFn.d.ts +7 -1
  24. package/dist/types/GitPushCommandFn.d.ts.map +1 -1
  25. package/dist/types/PostCommandExecFn.d.ts +2 -0
  26. package/dist/types/PostCommandExecFn.d.ts.map +1 -1
  27. package/dist/usages/runLump/main.d.ts +6 -1
  28. package/dist/usages/runLump/main.d.ts.map +1 -1
  29. package/dist/utils/commitMessageIncludesMarker/index.d.ts +2 -0
  30. package/dist/utils/commitMessageIncludesMarker/index.d.ts.map +1 -0
  31. package/dist/utils/commitMessageIncludesMarker/main.d.ts +3 -0
  32. package/dist/utils/commitMessageIncludesMarker/main.d.ts.map +1 -0
  33. package/dist/utils/index.d.ts +2 -0
  34. package/dist/utils/index.d.ts.map +1 -1
  35. package/dist/utils/parseGitLogHashBodyRecords/index.d.ts +2 -0
  36. package/dist/utils/parseGitLogHashBodyRecords/index.d.ts.map +1 -0
  37. package/dist/utils/parseGitLogHashBodyRecords/main.d.ts +8 -0
  38. package/dist/utils/parseGitLogHashBodyRecords/main.d.ts.map +1 -0
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3403,6 +3403,40 @@ function parseGitLogHashSubjectLines(stdout) {
3403
3403
  });
3404
3404
  }
3405
3405
 
3406
+ const GIT_LOG_HASH_BODY_FORMAT = '%x1e%H%x00%B';
3407
+ const RECORD_SEPARATOR = '\x1e';
3408
+ const HASH_MESSAGE_SEPARATOR = '\x00';
3409
+ /** Parses `git log --format='%x1e%H%x00%B'` stdout into commit hash and full-message pairs. */
3410
+ function parseGitLogHashBodyRecords(stdout) {
3411
+ const records = [];
3412
+ for (const record of stdout.split(RECORD_SEPARATOR)) {
3413
+ if (!record)
3414
+ continue;
3415
+ const nul = record.indexOf(HASH_MESSAGE_SEPARATOR);
3416
+ if (nul === -1)
3417
+ continue;
3418
+ const hash = record.slice(0, nul).trim();
3419
+ if (!hash)
3420
+ continue;
3421
+ records.push({
3422
+ hash,
3423
+ message: record.slice(nul + 1),
3424
+ });
3425
+ }
3426
+ return records;
3427
+ }
3428
+
3429
+ const MARKER_CONTINUE = '[A-Za-z0-9_-]';
3430
+ function escapeRegExp(value) {
3431
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3432
+ }
3433
+ /** True when `marker` occurs in `message` and is not a prefix of a longer `[A-Za-z0-9_-]*` token. */
3434
+ function commitMessageIncludesMarker(message, marker) {
3435
+ if (marker.length === 0)
3436
+ return false;
3437
+ return new RegExp(`${escapeRegExp(marker)}(?!${MARKER_CONTINUE})`).test(message);
3438
+ }
3439
+
3406
3440
  /** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
3407
3441
  function nodeErrnoCode(error) {
3408
3442
  if (error && typeof error === 'object' && 'code' in error) {
@@ -3745,6 +3779,75 @@ async function collectStepsForContext(params) {
3745
3779
  return collectedSteps;
3746
3780
  }
3747
3781
 
3782
+ const ABORT_STEP_WALK_MESSAGE = 'Process aborted';
3783
+ function stepWalkAbortedFailure() {
3784
+ return {
3785
+ success: false,
3786
+ data: {
3787
+ message: ABORT_STEP_WALK_MESSAGE,
3788
+ reason: 'stepWalkFailed',
3789
+ },
3790
+ };
3791
+ }
3792
+ function createStepWalkAbortError() {
3793
+ const error = new Error(ABORT_STEP_WALK_MESSAGE);
3794
+ error.name = 'AbortError';
3795
+ return error;
3796
+ }
3797
+ function isStepWalkAbortError(error) {
3798
+ return (typeof error === 'object'
3799
+ && error !== null
3800
+ && 'name' in error
3801
+ && error.name === 'AbortError');
3802
+ }
3803
+ /**
3804
+ * Await user-hook work, but stop waiting once `signal` aborts so the walk can
3805
+ * unwind (locks/teardown). Does not cancel sync busy-loops on the event loop.
3806
+ */
3807
+ async function awaitUnlessAborted(work, signal) {
3808
+ if (!signal) {
3809
+ return await work;
3810
+ }
3811
+ signal.throwIfAborted();
3812
+ return new Promise((resolve, reject) => {
3813
+ const onAbort = () => {
3814
+ cleanup();
3815
+ reject(createStepWalkAbortError());
3816
+ };
3817
+ const cleanup = () => {
3818
+ signal.removeEventListener('abort', onAbort);
3819
+ };
3820
+ signal.addEventListener('abort', onAbort, { once: true });
3821
+ Promise.resolve(work).then((value) => {
3822
+ cleanup();
3823
+ resolve(value);
3824
+ }, (error) => {
3825
+ cleanup();
3826
+ reject(error);
3827
+ });
3828
+ });
3829
+ }
3830
+ async function runOptionalGitCommand(input) {
3831
+ const { label, getCommand, cwd, logger } = input;
3832
+ try {
3833
+ const command = await getCommand();
3834
+ if (command == null || command === '') {
3835
+ return 'ok';
3836
+ }
3837
+ const result = await execAsync(command, { cwd });
3838
+ logger.verbose(`${label} ${JSON.stringify(result)}`);
3839
+ if (!result.success) {
3840
+ logger.error(formatExecFailureMessage({ label, failure: result }));
3841
+ return 'failed';
3842
+ }
3843
+ return 'ok';
3844
+ }
3845
+ catch (error) {
3846
+ const message = error instanceof Error ? error.message : String(error);
3847
+ logger.error(`Failed to run ${label}: ${message}`);
3848
+ return 'failed';
3849
+ }
3850
+ }
3748
3851
  async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
3749
3852
  const logger = loggerInput ?? createConsoleLogger({});
3750
3853
  const contextNames = contextList.map(context => context.name);
@@ -3783,6 +3886,10 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
3783
3886
  let runFailure;
3784
3887
  try {
3785
3888
  for (let i = 0; i < contextList.length; i++) {
3889
+ if (signal?.aborted) {
3890
+ runFailure = stepWalkAbortedFailure();
3891
+ break;
3892
+ }
3786
3893
  const context = contextList[i];
3787
3894
  logger.info(contextList.length > 1
3788
3895
  ? `Running context "${context.name}" (${i + 1}/${contextList.length})`
@@ -3795,127 +3902,137 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
3795
3902
  const contextRunState = setupResult?.contextRunState || {};
3796
3903
  let stepWalkFailure;
3797
3904
  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({
3905
+ try {
3906
+ for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
3907
+ if (stepWalkFailure) {
3908
+ return;
3909
+ }
3910
+ signal?.throwIfAborted();
3911
+ const step = stepsToExec[stepIndex];
3912
+ const nextCallHeadIndex = [...currStepIndex, stepIndex];
3913
+ const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
3914
+ if (typeof step === 'function' || Array.isArray(step)) {
3915
+ const subSteps = typeof step === 'function'
3916
+ ? await awaitUnlessAborted(step({
3917
+ context,
3918
+ stepIndex: compositeStepIndex,
3919
+ contextRunState,
3920
+ lumpVariables,
3921
+ }), signal)
3922
+ : step;
3923
+ await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
3924
+ continue;
3925
+ }
3926
+ logger.verbose(`step ${JSON.stringify(step)}`);
3927
+ const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
3928
+ const prompt = promptFn
3929
+ ? await awaitUnlessAborted(promptFn({
3809
3930
  context,
3810
3931
  stepIndex: compositeStepIndex,
3811
3932
  contextRunState,
3812
3933
  lumpVariables,
3934
+ stepVariables,
3935
+ }), signal)
3936
+ : '';
3937
+ const command = await awaitUnlessAborted(commandFn({
3938
+ context,
3939
+ prompt,
3940
+ stepIndex: compositeStepIndex,
3941
+ contextRunState,
3942
+ lumpVariables,
3943
+ stepVariables,
3944
+ projectRoot,
3945
+ workspacePath,
3946
+ }), signal);
3947
+ let commandResult = '';
3948
+ let commandSucceeded = true;
3949
+ if (command != null) {
3950
+ const { executable, args, env } = command;
3951
+ logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
3952
+ if (env != null) {
3953
+ logger.verbose(`command env overrides ${JSON.stringify(env)}`);
3954
+ }
3955
+ logger.verbose(`workspacePath ${workspacePath}`);
3956
+ const commandExec = await execBinary({
3957
+ binaryPath: executable,
3958
+ args,
3959
+ timeoutMillis,
3960
+ stdio: ['inherit', 'pipe', 'pipe'],
3961
+ cwd: workspacePath,
3962
+ signal,
3963
+ ...(env != null ? { env: { ...process.env, ...env } } : {}),
3813
3964
  });
3965
+ logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
3966
+ if (!commandExec.success) {
3967
+ const aborted = commandExec.data.reason === 'aborted';
3968
+ if (aborted || !continueOnError) {
3969
+ stepWalkFailure = {
3970
+ success: false,
3971
+ data: {
3972
+ message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
3973
+ reason: 'stepWalkFailed',
3974
+ },
3975
+ };
3976
+ return;
3977
+ }
3978
+ commandSucceeded = false;
3979
+ commandResult = (commandExec.data.stdout
3980
+ || commandExec.data.stderr
3981
+ || commandExec.data.message
3982
+ || '').toString();
3983
+ logger.verbose(`commandResult ${commandResult}`);
3984
+ }
3985
+ else {
3986
+ commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
3987
+ logger.verbose(`commandResult ${commandResult}`);
3988
+ }
3989
+ if (commandSucceeded) {
3990
+ const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
3991
+ logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
3992
+ }
3814
3993
  }
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({
3994
+ const historyEntry = {
3995
+ commandResult,
3996
+ commandSucceeded,
3825
3997
  context,
3998
+ prompt,
3826
3999
  stepIndex: compositeStepIndex,
3827
4000
  contextRunState,
3828
4001
  lumpVariables,
3829
4002
  stepVariables,
3830
- })
3831
- : '';
3832
- const command = await commandFn({
3833
- context,
3834
- prompt,
3835
- stepIndex: compositeStepIndex,
3836
- contextRunState,
3837
- lumpVariables,
3838
- stepVariables,
3839
- projectRoot,
3840
- workspacePath,
3841
- });
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,
4003
+ projectRoot,
4004
+ };
4005
+ const postCommandExecFnInput = {
4006
+ ...historyEntry,
3857
4007
  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;
4008
+ };
4009
+ logger.verbose(`context is ${JSON.stringify(context)}`);
4010
+ const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
4011
+ logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
4012
+ if (!!command && keepHistoryFilePath.length > 0) {
4013
+ const appendResult = await appendHistoryEntry({
4014
+ filePath: keepHistoryFilePath,
4015
+ entry: historyEntry,
4016
+ });
4017
+ if (!appendResult.success) {
4018
+ // TODO: sanitize history appending to avoid this warning for certain commands outputs
4019
+ logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
3872
4020
  }
3873
- commandSucceeded = false;
3874
- commandResult = (commandExec.data.stdout
3875
- || commandExec.data.stderr
3876
- || commandExec.data.message
3877
- || '').toString();
3878
- logger.verbose(`commandResult ${commandResult}`);
3879
4021
  }
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)}`);
3887
- }
3888
- }
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}`);
4022
+ if (postCommandExecFn) {
4023
+ const returnedSteps = await awaitUnlessAborted(postCommandExecFn(postCommandExecFnInput), signal);
4024
+ if (returnedSteps != null && returnedSteps.length > 0) {
4025
+ await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
4026
+ }
3911
4027
  }
3912
4028
  }
3913
- if (postCommandExecFn) {
3914
- const returnedSteps = await postCommandExecFn(postCommandExecFnInput);
3915
- if (returnedSteps != null && returnedSteps.length > 0) {
3916
- await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
3917
- }
4029
+ }
4030
+ catch (error) {
4031
+ if (isStepWalkAbortError(error)) {
4032
+ stepWalkFailure = stepWalkAbortedFailure();
4033
+ return;
3918
4034
  }
4035
+ throw error;
3919
4036
  }
3920
4037
  }
3921
4038
  try {
@@ -3943,39 +4060,39 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
3943
4060
  ...injectedGitAndWorkspaceFnsInput,
3944
4061
  context,
3945
4062
  };
3946
- const gitAddCommand = await execAsync(gitAddCommandFn(perContextInput), { cwd: workspacePath });
3947
- logger.verbose(`gitAddCommand ${JSON.stringify(gitAddCommand)}`);
3948
- if (!gitAddCommand.success) {
4063
+ const addOutcome = await runOptionalGitCommand({
4064
+ label: `git add for context ${context.name}`,
4065
+ getCommand: () => gitAddCommandFn(perContextInput),
4066
+ cwd: workspacePath,
4067
+ logger,
4068
+ });
4069
+ if (addOutcome === 'failed') {
3949
4070
  runFailure = {
3950
4071
  success: false,
3951
4072
  data: {
3952
- message: `Failed to add the changes for context ${context.name}: ${gitAddCommand.data.message}`,
4073
+ message: `Failed to add the changes for context ${context.name}`,
3953
4074
  },
3954
4075
  };
3955
4076
  break;
3956
4077
  }
3957
4078
  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
- }
4079
+ await runOptionalGitCommand({
4080
+ label: `git commit for context ${context.name}`,
4081
+ getCommand: () => gitCommitCommandFn({
4082
+ ...perContextInput,
4083
+ commitMessage,
4084
+ }),
4085
+ cwd: workspacePath,
4086
+ logger,
4087
+ });
3969
4088
  }
3970
4089
  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
- }
4090
+ await runOptionalGitCommand({
4091
+ label: `git push on branch ${branchName}`,
4092
+ getCommand: () => gitPushCommandFn(injectedGitAndWorkspaceFnsInput),
4093
+ cwd: workspacePath,
4094
+ logger,
4095
+ });
3979
4096
  }
3980
4097
  }
3981
4098
  finally {
@@ -4091,25 +4208,41 @@ async function scanDirectory({ dirPath, allPaths, gitignoresMap, projectRoot, lo
4091
4208
  }
4092
4209
  }
4093
4210
 
4211
+ /**
4212
+ * Refreshes remote-tracking refs for status / planning (one network fetch).
4213
+ * Uses `--no-write-fetch-head` so concurrent preflight/pull paths are safer.
4214
+ */
4215
+ const refreshRemoteTrackingRefs = async (input) => {
4216
+ const { projectRoot, remoteName = 'origin' } = input;
4217
+ const result = await execAsync(`git fetch --prune --no-write-fetch-head ${remoteName}`, { cwd: projectRoot });
4218
+ if (!result.success) {
4219
+ return failure(result.data.message);
4220
+ }
4221
+ return success(undefined);
4222
+ };
4223
+
4094
4224
  async function getContextStatus(params) {
4095
- const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, } = params;
4225
+ const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, skipFetch = false, } = params;
4096
4226
  const lumpVariables = (lumpVariablesInput ?? {});
4097
4227
  const commitMessage = gitCommitMessageFn({
4098
4228
  context: { name: contextName, variables: contextVariables },
4099
4229
  lumpVariables,
4100
4230
  baseBranch,
4101
4231
  });
4102
- const fetchAll = await execAsync(`git fetch --all --prune`, { cwd: projectRoot });
4103
- if (!fetchAll.success)
4104
- return 'toDo';
4105
- const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote('%H %s')}`, { cwd: projectRoot });
4232
+ if (!skipFetch) {
4233
+ const fetchResult = await refreshRemoteTrackingRefs({ projectRoot, remoteName });
4234
+ if (!fetchResult.success)
4235
+ return 'toDo';
4236
+ }
4237
+ const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(GIT_LOG_HASH_BODY_FORMAT)}`, { cwd: projectRoot });
4106
4238
  if (!logResult.success)
4107
4239
  return 'toDo';
4108
4240
  logger?.verbose(`logResult ${JSON.stringify(logResult.data)}`);
4109
4241
  const logResultOutput = logResult.data.stdout || logResult.data.stderr || '';
4110
- const matchingHashes = parseGitLogHashSubjectLines(logResultOutput)
4111
- .filter((entry) => entry.subject === commitMessage)
4242
+ const matchingHashes = parseGitLogHashBodyRecords(logResultOutput)
4243
+ .filter((entry) => commitMessageIncludesMarker(entry.message, commitMessage))
4112
4244
  .map((entry) => entry.hash);
4245
+ logger?.verbose(`contextName ${contextName}`);
4113
4246
  logger?.verbose(`remoteName ${remoteName}`);
4114
4247
  logger?.verbose(`baseBranch ${baseBranch}`);
4115
4248
  logger?.verbose(`commitMessage ${commitMessage}`);
@@ -4164,7 +4297,7 @@ function validateContextListNames(contextList) {
4164
4297
  }
4165
4298
 
4166
4299
  async function getToDoContextList(params) {
4167
- const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger } = params;
4300
+ const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger, refreshRemoteTrackingRefsFn = refreshRemoteTrackingRefs, } = params;
4168
4301
  const codeBasePathsResult = await getCodeBasePaths({ cwd: projectRoot, logger });
4169
4302
  if (!codeBasePathsResult.success) {
4170
4303
  return failure({
@@ -4182,21 +4315,28 @@ async function getToDoContextList(params) {
4182
4315
  const allCtxNames = contextList.flatMap(context => [context.name, ...(context.options?.dependsOnContexts ?? [])]);
4183
4316
  const allCtxNamesSet = new Set(allCtxNames);
4184
4317
  const allCtxNamesList = Array.from(allCtxNamesSet);
4185
- const contextStatusList = await Promise.all(allCtxNamesList.map((contextName) => {
4186
- return getContextStatus({
4187
- contextName: contextName,
4318
+ const refreshResult = await refreshRemoteTrackingRefsFn({ projectRoot });
4319
+ let contextStatusMap;
4320
+ if (!refreshResult.success) {
4321
+ logger?.warn(`Failed to refresh remote-tracking refs for context status; treating contexts as toDo: ${refreshResult.data}`);
4322
+ contextStatusMap = new Map(allCtxNamesList.map((name) => [name, 'toDo']));
4323
+ }
4324
+ else {
4325
+ const contextStatusList = await Promise.all(allCtxNamesList.map((contextName) => getContextStatus({
4326
+ contextName,
4188
4327
  contextVariables: {}, // TODO: Remove contextVariables from getContextStatus, really not needed
4189
4328
  gitCommitMessageFn,
4190
4329
  lumpVariables,
4191
4330
  projectRoot,
4192
4331
  baseBranch,
4193
4332
  logger,
4194
- });
4195
- }));
4196
- const contextStatusMap = new Map(allCtxNamesList.map((contextName, i) => [contextName, contextStatusList[i]]));
4333
+ skipFetch: true,
4334
+ })));
4335
+ contextStatusMap = new Map(allCtxNamesList.map((contextName, i) => [contextName, contextStatusList[i]]));
4336
+ }
4197
4337
  const contextListToDo = contextList
4198
- .filter((context, contextIndex) => {
4199
- const contextStatus = contextStatusList[contextIndex];
4338
+ .filter((context) => {
4339
+ const contextStatus = contextStatusMap.get(context.name);
4200
4340
  if (contextStatus && contextStatus !== 'toDo')
4201
4341
  return false;
4202
4342
  const deps = context.options?.dependsOnContexts;
@@ -4262,7 +4402,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
4262
4402
  };
4263
4403
 
4264
4404
  async function runLump(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;
4405
+ 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;
4266
4406
  const lumpVariables = (lumpVariablesInput ?? {});
4267
4407
  const logger = loggerInput ?? createConsoleLogger({});
4268
4408
  const contextListToDoResult = await getToDoContextList({
@@ -4272,6 +4412,7 @@ async function runLump(input) {
4272
4412
  baseBranch,
4273
4413
  gitCommitMessageFn,
4274
4414
  logger,
4415
+ refreshRemoteTrackingRefsFn,
4275
4416
  });
4276
4417
  if (!contextListToDoResult.success) {
4277
4418
  return set(contextListToDoResult, ['data', 'message'], "Error in runLump: Failed to get to do context list. Original Error: " + contextListToDoResult.data.message);
@@ -4318,5 +4459,5 @@ async function runLump(input) {
4318
4459
  });
4319
4460
  }
4320
4461
 
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 };
4462
+ export { GIT_LOG_HASH_BODY_FORMAT, appendHistoryEntry, collectStepsForContext, commitMessageIncludesMarker, contextStatus, createConsoleLogger, defaultGitAddCommandFn, defaultGitCommitCommandFn, defaultGitCommitMessageFn, defaultGitPushCommandFn, defaultSetupWorkspaceFn, defaultSetupWorkspaceFnWithWorktree, defaultTeardownWorkspaceFn, defaultTeardownWorkspaceFnWithWorktree, execAsync, execBinary, executeStepsForContextList, failure, formatExecFailureMessage, getCodeBasePaths, getContextStatus, getToDoContextList, historyFormatFromPath, isProcessAlive, killProcessTree, nodeErrnoCode, parseGitLogHashBodyRecords, parseGitLogHashSubjectLines, pathExists, readHistoryFile, refreshRemoteTrackingRefs, resolveSpawnExecutable, runLump, set, shellSingleQuote, success, validateContextListNames, windowsNpmCmdShimBody, writeHistoryFile };
4322
4463
  //# sourceMappingURL=index.js.map