git-stack-cli 1.14.0 → 1.15.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 (49) hide show
  1. package/README.md +2 -4
  2. package/dist/cjs/index.cjs +310 -180
  3. package/package.json +2 -1
  4. package/scripts/link.ts +14 -0
  5. package/src/app/App.tsx +41 -30
  6. package/src/app/AutoUpdate.tsx +9 -24
  7. package/src/app/CherryPickCheck.tsx +1 -2
  8. package/src/app/Debug.tsx +5 -6
  9. package/src/app/DependencyCheck.tsx +6 -6
  10. package/src/app/DetectInitialPR.tsx +2 -8
  11. package/src/app/DirtyCheck.tsx +51 -26
  12. package/src/app/Exit.tsx +41 -8
  13. package/src/app/FormatText.tsx +1 -5
  14. package/src/app/GatherMetadata.tsx +6 -13
  15. package/src/app/GithubApiError.tsx +1 -1
  16. package/src/app/HandleCtrlCSigint.tsx +36 -0
  17. package/src/app/LocalCommitStatus.tsx +1 -3
  18. package/src/app/LogTimestamp.tsx +1 -5
  19. package/src/app/ManualRebase.tsx +15 -37
  20. package/src/app/MultiSelect.tsx +2 -2
  21. package/src/app/PostRebaseStatus.tsx +2 -0
  22. package/src/app/PreManualRebase.tsx +3 -5
  23. package/src/app/RebaseCheck.tsx +1 -2
  24. package/src/app/SelectCommitRanges.tsx +6 -10
  25. package/src/app/Status.tsx +1 -1
  26. package/src/app/StatusTable.tsx +1 -4
  27. package/src/app/Store.tsx +29 -3
  28. package/src/app/SyncGithub.tsx +15 -45
  29. package/src/app/Table.tsx +4 -14
  30. package/src/app/TextInput.tsx +2 -7
  31. package/src/app/VerboseDebugInfo.tsx +1 -5
  32. package/src/app/YesNoPrompt.tsx +42 -31
  33. package/src/command.ts +8 -17
  34. package/src/commands/Fixup.tsx +17 -24
  35. package/src/commands/Log.tsx +3 -7
  36. package/src/commands/Rebase.tsx +18 -38
  37. package/src/components/ErrorBoundary.tsx +79 -0
  38. package/src/components/ExitingGate.tsx +27 -0
  39. package/src/core/CommitMetadata.ts +1 -1
  40. package/src/core/GitReviseTodo.test.ts +3 -3
  41. package/src/core/GitReviseTodo.ts +6 -8
  42. package/src/core/Metadata.test.ts +4 -4
  43. package/src/core/StackSummaryTable.ts +3 -3
  44. package/src/core/chalk.ts +1 -5
  45. package/src/core/cli.ts +2 -2
  46. package/src/core/github.tsx +15 -14
  47. package/src/core/pretty_json.ts +7 -0
  48. package/src/github/gh.auth_status.test.ts +2 -6
  49. package/src/index.tsx +42 -6
@@ -17667,33 +17667,42 @@ function Parens(props) {
17667
17667
 
17668
17668
  function YesNoPrompt(props) {
17669
17669
  const [answer, set_answer] = reactExports.useState("");
17670
+ const answered_ref = reactExports.useRef(false);
17670
17671
  useInput((input) => {
17671
- const inputLower = input.toLowerCase();
17672
- set_answer(inputLower);
17673
- switch (inputLower) {
17672
+ // prevent answering multiple times
17673
+ if (answered_ref.current) {
17674
+ return;
17675
+ }
17676
+ const input_lower = input.toLowerCase();
17677
+ let handler;
17678
+ switch (input_lower) {
17674
17679
  case "n":
17675
- return props.onNo();
17680
+ handler = props.onNo;
17681
+ break;
17676
17682
  case "y":
17677
- return props.onYes();
17683
+ handler = props.onYes;
17684
+ break;
17685
+ }
17686
+ // handler if valid answer (y or n)
17687
+ if (handler) {
17688
+ answered_ref.current = true;
17689
+ set_answer(input_lower);
17690
+ handler();
17678
17691
  }
17679
17692
  });
17680
- // prettier-ignore
17681
- const y = reactExports.createElement(Text, { bold: true, color: colors.green }, "Y");
17682
- const n = reactExports.createElement(Text, { color: colors.red }, "n");
17683
- let choices;
17684
- switch (answer) {
17685
- case "y":
17686
- choices = y;
17687
- break;
17688
- case "n":
17689
- choices = n;
17690
- break;
17691
- default:
17692
- choices = (reactExports.createElement(reactExports.Fragment, null,
17693
- y,
17694
- reactExports.createElement(Text, null, "/"),
17695
- n));
17696
- }
17693
+ const choices = (function get_choices() {
17694
+ // prettier-ignore
17695
+ const y = reactExports.createElement(Text, { bold: true, color: colors.green }, "Y");
17696
+ const n = reactExports.createElement(Text, { color: colors.red }, "n");
17697
+ switch (answer) {
17698
+ case "y":
17699
+ return y;
17700
+ case "n":
17701
+ return n;
17702
+ default:
17703
+ return reactExports.createElement(FormatText, { message: "{y}/{n}", values: { y, n } });
17704
+ }
17705
+ })();
17697
17706
  return (reactExports.createElement(Box, { flexDirection: "column" },
17698
17707
  reactExports.createElement(Box, { alignItems: "flex-end" },
17699
17708
  typeof props.message === "object" ? (props.message) : (reactExports.createElement(Text, { color: colors.yellow }, props.message)),
@@ -18487,15 +18496,41 @@ const immerImpl = (initializer) => (set, get, store) => {
18487
18496
  };
18488
18497
  const immer = immerImpl;
18489
18498
 
18499
+ async function sleep(time) {
18500
+ return new Promise((resolve) => setTimeout(resolve, time));
18501
+ }
18502
+
18490
18503
  function Exit(props) {
18491
- const actions = Store.useActions();
18492
18504
  reactExports.useEffect(() => {
18493
- if (props.clear) {
18494
- actions.clear();
18505
+ // immediately handle exit on mount
18506
+ handle_exit().catch((err) => {
18507
+ // eslint-disable-next-line no-console
18508
+ console.error(err);
18509
+ });
18510
+ async function handle_exit() {
18511
+ const state = Store.getState();
18512
+ const actions = state.actions;
18513
+ actions.debug(`[Exit] handle_exit ${JSON.stringify(props)}`);
18514
+ let exit_code = props.code;
18515
+ // run abort_handler if it exists
18516
+ if (state.abort_handler) {
18517
+ exit_code = await state.abort_handler();
18518
+ }
18519
+ // restore git stash if necessary
18520
+ if (state.is_dirty_check_stash) {
18521
+ await cli("git stash pop");
18522
+ actions.output(reactExports.createElement(Text, { color: colors.green }, "\u2705 Changes restored from stash"));
18523
+ }
18524
+ // ensure output has a chance to render
18525
+ await sleep(1);
18526
+ // finally handle the actual app and process exit
18527
+ if (props.clear) {
18528
+ actions.clear();
18529
+ }
18530
+ actions.unmount();
18531
+ process.exitCode = exit_code;
18532
+ process.exit();
18495
18533
  }
18496
- actions.unmount();
18497
- process.exitCode = props.code;
18498
- process.exit();
18499
18534
  }, [props.clear, props.code]);
18500
18535
  return null;
18501
18536
  }
@@ -26194,7 +26229,11 @@ function friendlyDateTime(dateTimeish) {
26194
26229
  }
26195
26230
 
26196
26231
  function LogTimestamp() {
26197
- return (reactExports.createElement(Text, { dimColor: true }, DateTime.now().toFormat("[yyyy-MM-dd HH:mm:ss.SSS] ")));
26232
+ return reactExports.createElement(Text, { dimColor: true }, DateTime.now().toFormat("[yyyy-MM-dd HH:mm:ss.SSS] "));
26233
+ }
26234
+
26235
+ function pretty_json(input) {
26236
+ return JSON.stringify(input, null, 2);
26198
26237
  }
26199
26238
 
26200
26239
  const BaseStore = createStore()(immer((set, get) => ({
@@ -26214,6 +26253,9 @@ const BaseStore = createStore()(immer((set, get) => ({
26214
26253
  pr_templates: [],
26215
26254
  pr_template_body: null,
26216
26255
  sync_github: null,
26256
+ is_dirty_check_stash: false,
26257
+ abort_handler: null,
26258
+ is_exiting: false,
26217
26259
  step: "loading",
26218
26260
  output: [],
26219
26261
  pending_output: {},
@@ -26221,6 +26263,7 @@ const BaseStore = createStore()(immer((set, get) => ({
26221
26263
  actions: {
26222
26264
  exit(code, clear = true) {
26223
26265
  set((state) => {
26266
+ state.is_exiting = true;
26224
26267
  const node = reactExports.createElement(Exit, { clear: clear, code: code });
26225
26268
  state.mutate.output(state, { node });
26226
26269
  });
@@ -26239,7 +26282,7 @@ const BaseStore = createStore()(immer((set, get) => ({
26239
26282
  },
26240
26283
  json(value) {
26241
26284
  set((state) => {
26242
- const node = JSON.stringify(value, null, 2);
26285
+ const node = pretty_json(value);
26243
26286
  state.mutate.output(state, { node });
26244
26287
  });
26245
26288
  },
@@ -26276,6 +26319,16 @@ const BaseStore = createStore()(immer((set, get) => ({
26276
26319
  state.pr = {};
26277
26320
  });
26278
26321
  },
26322
+ register_abort_handler(abort_handler) {
26323
+ set((state) => {
26324
+ state.abort_handler = abort_handler;
26325
+ });
26326
+ },
26327
+ unregister_abort_handler() {
26328
+ set((state) => {
26329
+ state.abort_handler = null;
26330
+ });
26331
+ },
26279
26332
  set(setter) {
26280
26333
  set((state) => {
26281
26334
  setter(state);
@@ -26561,10 +26614,6 @@ function semver_compare(version_a, version_b) {
26561
26614
  return 0;
26562
26615
  }
26563
26616
 
26564
- async function sleep(time) {
26565
- return new Promise((resolve) => setTimeout(resolve, time));
26566
- }
26567
-
26568
26617
  function reducer$4(state, patch) {
26569
26618
  return { ...state, ...patch };
26570
26619
  }
@@ -26596,9 +26645,7 @@ function AutoUpdate(props) {
26596
26645
  if (props_ref.current.verbose) {
26597
26646
  handle_output(reactExports.createElement(Text, { key: "init" }, "Checking for latest version..."));
26598
26647
  }
26599
- const timeout_ms = is_finite_value(props.timeoutMs)
26600
- ? props.timeoutMs
26601
- : 2 * 1000;
26648
+ const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
26602
26649
  const npm_json = await Promise.race([
26603
26650
  fetch_json(`https://registry.npmjs.org/${props.name}`),
26604
26651
  sleep(timeout_ms).then(() => {
@@ -26659,8 +26706,8 @@ function AutoUpdate(props) {
26659
26706
  case "prompt":
26660
26707
  return (reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(Text, { color: colors.yellow }, "New version available, would you like to update?"), onYes: async () => {
26661
26708
  handle_output(reactExports.createElement(FormatText, { key: "install", wrapper: reactExports.createElement(Text, null), message: "Installing {name}@{version}...", values: {
26662
- name: (reactExports.createElement(Text, { color: colors.yellow }, props.name)),
26663
- version: (reactExports.createElement(Text, { color: colors.blue }, state.latest_version)),
26709
+ name: reactExports.createElement(Text, { color: colors.yellow }, props.name),
26710
+ version: reactExports.createElement(Text, { color: colors.blue }, state.latest_version),
26664
26711
  } }));
26665
26712
  patch({ status: "install" });
26666
26713
  await cli(`npm install -g ${props.name}@latest`);
@@ -26898,7 +26945,7 @@ function Debug() {
26898
26945
  const output_file = path.join(state.cwd, "git-stack-state.json");
26899
26946
  await safe_rm(output_file);
26900
26947
  const serialized = serialize(state);
26901
- const content = JSON.stringify(serialized, null, 2);
26948
+ const content = pretty_json(serialized);
26902
26949
  await fs$1.writeFile(output_file, content);
26903
26950
  }
26904
26951
  }, [argv, state]);
@@ -29807,22 +29854,25 @@ async function pr_create(args) {
29807
29854
  }
29808
29855
  async function pr_edit(args) {
29809
29856
  const command_parts = [`gh pr edit ${args.branch} --base ${args.base}`];
29857
+ let body_file;
29810
29858
  if (args.body) {
29811
- const body_file = await write_body_file(args);
29859
+ body_file = await write_body_file(args);
29812
29860
  command_parts.push(`--body-file="${body_file}"`);
29813
29861
  }
29814
29862
  const cli_result = await cli(command_parts);
29815
29863
  if (cli_result.code !== 0) {
29816
29864
  handle_error(cli_result.output);
29817
29865
  }
29866
+ // cleanup body_file
29867
+ if (body_file) {
29868
+ await safe_rm(body_file);
29869
+ }
29818
29870
  }
29819
29871
  async function pr_draft(args) {
29820
29872
  // https://cli.github.com/manual/gh_api
29821
29873
  // https://docs.github.com/en/graphql/reference/mutations#convertpullrequesttodraft
29822
29874
  // https://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview
29823
- const mutation_name = args.draft
29824
- ? "convertPullRequestToDraft"
29825
- : "markPullRequestReadyForReview";
29875
+ const mutation_name = args.draft ? "convertPullRequestToDraft" : "markPullRequestReadyForReview";
29826
29876
  let query = `
29827
29877
  mutation($id: ID!) {
29828
29878
  ${mutation_name}(input: { pullRequestId: $id }) {
@@ -29841,9 +29891,7 @@ async function pr_draft(args) {
29841
29891
  const state = Store.getState();
29842
29892
  const cache_pr = state.pr[args.branch];
29843
29893
  invariant(cache_pr, "cache_pr must exist");
29844
- const command_parts = [
29845
- `gh api graphql -F id="${cache_pr.id}" -f query='${query}'`,
29846
- ];
29894
+ const command_parts = [`gh api graphql -F id="${cache_pr.id}" -f query='${query}'`];
29847
29895
  const command = command_parts.join(" ");
29848
29896
  const cli_result = await cli(command);
29849
29897
  if (cli_result.code !== 0) {
@@ -30186,6 +30234,8 @@ echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
30186
30234
  // change to pipe to see output temporarily
30187
30235
  // https://github.com/magus/git-stack-cli/commit/f9f10e3ac3cd9a35ee75d3e0851a48391967a23f
30188
30236
  await cli(command, { stdio: ["ignore", "ignore", "ignore"] });
30237
+ // cleanup tmp_git_sequence_editor_path
30238
+ await safe_rm(tmp_git_sequence_editor_path);
30189
30239
  };
30190
30240
 
30191
30241
  function reducer$2(state, patch) {
@@ -30301,20 +30351,28 @@ function DirtyCheck(props) {
30301
30351
  switch (state.status) {
30302
30352
  case "done":
30303
30353
  return props.children;
30354
+ case "stash":
30355
+ return (reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "\uD83D\uDCE6 Stashing uncommitted changes\u2026" }));
30304
30356
  case "prompt":
30305
- return (reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(Box, { flexDirection: "column" },
30306
- reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git} repo has uncommitted changes.", values: {
30307
- git: reactExports.createElement(Command, null, "git"),
30308
- git_stack: reactExports.createElement(Command, null, "git stack"),
30309
- } }),
30310
- reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "Changes may be lost during {git_stack}, are you sure you want to proceed?", values: {
30311
- git: reactExports.createElement(Command, null, "git"),
30312
- git_stack: reactExports.createElement(Command, null, "git stack"),
30313
- } })), onYes: async () => {
30314
- patch({ status: "done" });
30315
- }, onNo: async () => {
30316
- actions.exit(0);
30317
- } }));
30357
+ return (reactExports.createElement(Box, { flexDirection: "column" },
30358
+ reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "\u26A0\uFE0F Uncommitted changes detected. {git_stack} needs a clean working tree.", values: {
30359
+ git: reactExports.createElement(Command, null, "git"),
30360
+ git_stack: reactExports.createElement(Command, null, "git stack"),
30361
+ } }),
30362
+ reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git_stash} changes to proceed?", values: {
30363
+ git_stash: reactExports.createElement(Command, null, "git stash"),
30364
+ } }), onYes: async () => {
30365
+ patch({ status: "stash" });
30366
+ await cli("git stash --include-untracked");
30367
+ actions.output(reactExports.createElement(Text, { color: colors.yellow },
30368
+ reactExports.createElement(FormatText, { message: "\uD83D\uDCE6 Changes saved to stash" })));
30369
+ actions.set((state) => {
30370
+ state.is_dirty_check_stash = true;
30371
+ });
30372
+ patch({ status: "done" });
30373
+ }, onNo: async () => {
30374
+ actions.exit(0);
30375
+ } })));
30318
30376
  default:
30319
30377
  return (reactExports.createElement(Await, { function: run, fallback: reactExports.createElement(Text, { color: colors.yellow },
30320
30378
  "Ensuring ",
@@ -30325,8 +30383,12 @@ function DirtyCheck(props) {
30325
30383
  const actions = Store.getState().actions;
30326
30384
  try {
30327
30385
  const git_dirty = (await cli(`git status --porcelain`)).stdout;
30328
- const status = git_dirty ? "prompt" : "done";
30329
- patch({ status });
30386
+ if (!git_dirty) {
30387
+ patch({ status: "done" });
30388
+ }
30389
+ else {
30390
+ patch({ status: "prompt" });
30391
+ }
30330
30392
  }
30331
30393
  catch (err) {
30332
30394
  actions.error("Must be run from within a git repository.");
@@ -30341,7 +30403,7 @@ function DirtyCheck(props) {
30341
30403
  }
30342
30404
 
30343
30405
  function GatherMetadata(props) {
30344
- const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Gathering local git information\u2026"));
30406
+ const fallback = reactExports.createElement(Text, { color: colors.yellow }, "Gathering local git information\u2026");
30345
30407
  return (reactExports.createElement(Await, { fallback: fallback, function: run$a }, props.children));
30346
30408
  }
30347
30409
  async function run$a() {
@@ -30383,8 +30445,7 @@ async function run$a() {
30383
30445
  return;
30384
30446
  }
30385
30447
  const head = (await cli("git rev-parse HEAD")).stdout;
30386
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
30387
- .stdout;
30448
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
30388
30449
  // handle when there are no detected changes
30389
30450
  if (head === merge_base) {
30390
30451
  actions.newline();
@@ -30476,9 +30537,29 @@ async function run$9() {
30476
30537
  reactExports.createElement(Text, { bold: true, color: colors.yellow }, time_until))));
30477
30538
  }
30478
30539
 
30540
+ function HandleCtrlCSigint() {
30541
+ const actions = Store.useActions();
30542
+ useInput((input, key) => {
30543
+ handle_input().catch((err) => {
30544
+ // eslint-disable-next-line no-console
30545
+ console.error(err);
30546
+ });
30547
+ async function handle_input() {
30548
+ if (input === "c" && key.ctrl) {
30549
+ actions.clear();
30550
+ actions.output(reactExports.createElement(Text, { color: colors.red },
30551
+ reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Ctrl+C detected" })));
30552
+ await sleep(1);
30553
+ actions.exit(235);
30554
+ }
30555
+ }
30556
+ });
30557
+ return null;
30558
+ }
30559
+
30479
30560
  function LocalCommitStatus(props) {
30480
30561
  const argv = Store.useState((state) => state.argv);
30481
- const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Fetching PR status from Github\u2026"));
30562
+ const fallback = reactExports.createElement(Text, { color: colors.yellow }, "Fetching PR status from Github\u2026");
30482
30563
  if (argv["mock-metadata"]) {
30483
30564
  return (reactExports.createElement(Await, { fallback: fallback, function: mock_metadata }, props.children));
30484
30565
  }
@@ -30572,21 +30653,9 @@ function encode(value) {
30572
30653
  }
30573
30654
 
30574
30655
  function Rebase() {
30575
- const abort_handler = reactExports.useRef(() => { });
30576
- reactExports.useEffect(function listen_sigint() {
30577
- process.once("SIGINT", sigint_handler);
30578
- return function cleanup() {
30579
- process.removeListener("SIGINT", sigint_handler);
30580
- };
30581
- function sigint_handler() {
30582
- abort_handler.current();
30583
- }
30584
- }, []);
30585
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
30586
- await Rebase.run({ abort_handler });
30587
- } }));
30656
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: Rebase.run }));
30588
30657
  }
30589
- Rebase.run = async function run(args) {
30658
+ Rebase.run = async function run() {
30590
30659
  const state = Store.getState();
30591
30660
  const actions = state.actions;
30592
30661
  const branch_name = state.branch_name;
@@ -30597,11 +30666,12 @@ Rebase.run = async function run(args) {
30597
30666
  invariant(branch_name, "branch_name must exist");
30598
30667
  invariant(commit_range, "commit_range must exist");
30599
30668
  invariant(repo_root, "repo_root must exist");
30600
- // always listen for SIGINT event and restore git state
30601
- args.abort_handler.current = async function sigint_handler() {
30669
+ // immediately register abort_handler in case of ctrl+c exit
30670
+ actions.register_abort_handler(async function abort_rebase() {
30602
30671
  actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
30603
- handle_exit(19);
30604
- };
30672
+ handle_exit();
30673
+ return 19;
30674
+ });
30605
30675
  const temp_branch_name = `${branch_name}_${short_id()}`;
30606
30676
  try {
30607
30677
  // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
@@ -30652,6 +30722,7 @@ Rebase.run = async function run(args) {
30652
30722
  branch_name: reactExports.createElement(Brackets, null, branch_name),
30653
30723
  origin_branch: reactExports.createElement(Brackets, null, `origin/${master_branch}`),
30654
30724
  } }));
30725
+ actions.unregister_abort_handler();
30655
30726
  actions.set((state) => {
30656
30727
  state.commit_range = next_commit_range;
30657
30728
  state.step = "status";
@@ -30664,7 +30735,8 @@ Rebase.run = async function run(args) {
30664
30735
  actions.error(err.message);
30665
30736
  }
30666
30737
  }
30667
- handle_exit(20);
30738
+ handle_exit();
30739
+ actions.exit(20);
30668
30740
  }
30669
30741
  // cleanup git operations if cancelled during manual rebase
30670
30742
  function restore_git() {
@@ -30686,7 +30758,7 @@ Rebase.run = async function run(args) {
30686
30758
  }
30687
30759
  cli.sync(`pwd`, spawn_options);
30688
30760
  }
30689
- function handle_exit(code) {
30761
+ function handle_exit() {
30690
30762
  actions.output(reactExports.createElement(Text, { color: colors.yellow },
30691
30763
  "Restoring ",
30692
30764
  reactExports.createElement(Brackets, null, branch_name),
@@ -30696,7 +30768,6 @@ Rebase.run = async function run(args) {
30696
30768
  "Restored ",
30697
30769
  reactExports.createElement(Brackets, null, branch_name),
30698
30770
  "."));
30699
- actions.exit(code);
30700
30771
  }
30701
30772
  };
30702
30773
 
@@ -30705,21 +30776,9 @@ function LocalMergeRebase() {
30705
30776
  }
30706
30777
 
30707
30778
  function ManualRebase() {
30708
- const abort_handler = reactExports.useRef(() => { });
30709
- reactExports.useEffect(function listen_sigint() {
30710
- process.once("SIGINT", sigint_handler);
30711
- return function cleanup() {
30712
- process.removeListener("SIGINT", sigint_handler);
30713
- };
30714
- async function sigint_handler() {
30715
- abort_handler.current();
30716
- }
30717
- }, []);
30718
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
30719
- await run$7({ abort_handler });
30720
- } }));
30779
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$7 }));
30721
30780
  }
30722
- async function run$7(args) {
30781
+ async function run$7() {
30723
30782
  const state = Store.getState();
30724
30783
  const actions = state.actions;
30725
30784
  const argv = state.argv;
@@ -30731,16 +30790,16 @@ async function run$7(args) {
30731
30790
  invariant(branch_name, "branch_name must exist");
30732
30791
  invariant(commit_map, "commit_map must exist");
30733
30792
  invariant(repo_root, "repo_root must exist");
30734
- // always listen for SIGINT event and restore git state
30735
- args.abort_handler.current = function sigint_handler() {
30793
+ // immediately register abort_handler in case of ctrl+c exit
30794
+ actions.register_abort_handler(async function abort_manual_rebase() {
30736
30795
  actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
30737
- handle_exit(15);
30738
- };
30796
+ handle_exit();
30797
+ return 15;
30798
+ });
30739
30799
  const temp_branch_name = `${branch_name}_${short_id()}`;
30740
30800
  try {
30741
30801
  // get latest merge_base relative to local master
30742
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
30743
- .stdout;
30802
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
30744
30803
  // immediately paint all commit to preserve selected commit ranges
30745
30804
  let commit_range = await range(commit_map);
30746
30805
  // reverse group list to ensure we create git revise in correct order
@@ -30790,6 +30849,7 @@ async function run$7(args) {
30790
30849
  // of original branch to the newly created temporary branch
30791
30850
  await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
30792
30851
  restore_git();
30852
+ actions.unregister_abort_handler();
30793
30853
  if (argv.sync) {
30794
30854
  actions.set((state) => {
30795
30855
  state.step = "sync-github";
@@ -30810,7 +30870,8 @@ async function run$7(args) {
30810
30870
  if (!argv.verbose) {
30811
30871
  actions.error("Try again with `--verbose` to see more information.");
30812
30872
  }
30813
- handle_exit(16);
30873
+ handle_exit();
30874
+ actions.exit(16);
30814
30875
  }
30815
30876
  // cleanup git operations if cancelled during manual rebase
30816
30877
  function restore_git() {
@@ -30832,7 +30893,7 @@ async function run$7(args) {
30832
30893
  }
30833
30894
  cli.sync(`pwd`, spawn_options);
30834
30895
  }
30835
- function handle_exit(code) {
30896
+ function handle_exit() {
30836
30897
  actions.output(reactExports.createElement(Text, { color: colors.yellow },
30837
30898
  "Restoring ",
30838
30899
  reactExports.createElement(Brackets, null, branch_name),
@@ -30842,7 +30903,6 @@ async function run$7(args) {
30842
30903
  "Restored ",
30843
30904
  reactExports.createElement(Brackets, null, branch_name),
30844
30905
  "."));
30845
- actions.exit(code);
30846
30906
  }
30847
30907
  }
30848
30908
 
@@ -31035,6 +31095,7 @@ async function run$6() {
31035
31095
  });
31036
31096
  actions.output(reactExports.createElement(StatusTable, null));
31037
31097
  actions.output(reactExports.createElement(Text, null, "\u2705 Everything up to date."));
31098
+ actions.exit(0);
31038
31099
  }
31039
31100
 
31040
31101
  function PreLocalMergeRebase() {
@@ -31095,7 +31156,7 @@ async function run$5() {
31095
31156
  state.pr_templates = pr_templates;
31096
31157
  if (pr_templates.length > 0) {
31097
31158
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{count} queryable templates found under {dir}, but not supported.", values: {
31098
- count: (reactExports.createElement(Text, { color: colors.blue }, pr_templates.length)),
31159
+ count: reactExports.createElement(Text, { color: colors.blue }, pr_templates.length),
31099
31160
  dir: reactExports.createElement(Brackets, null, PR_TEMPLATE.TemplateDir("")),
31100
31161
  } }));
31101
31162
  }
@@ -31757,21 +31818,9 @@ const RE = {
31757
31818
  };
31758
31819
 
31759
31820
  function SyncGithub() {
31760
- const abort_handler = reactExports.useRef(() => { });
31761
- reactExports.useEffect(function listen_sigint() {
31762
- process.once("SIGINT", sigint_handler);
31763
- return function cleanup() {
31764
- process.removeListener("SIGINT", sigint_handler);
31765
- };
31766
- function sigint_handler() {
31767
- abort_handler.current();
31768
- }
31769
- }, []);
31770
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: async function () {
31771
- await run$3({ abort_handler });
31772
- } }));
31821
+ return reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: run$3 });
31773
31822
  }
31774
- async function run$3(args) {
31823
+ async function run$3() {
31775
31824
  const state = Store.getState();
31776
31825
  const actions = state.actions;
31777
31826
  const argv = state.argv;
@@ -31786,11 +31835,12 @@ async function run$3(args) {
31786
31835
  invariant(sync_github, "sync_github must exist");
31787
31836
  const commit_range = sync_github.commit_range;
31788
31837
  const rebase_group_index = sync_github.rebase_group_index;
31789
- // always listen for SIGINT event and restore pr state
31790
- args.abort_handler.current = function sigint_handler() {
31838
+ // immediately register abort_handler in case of ctrl+c exit
31839
+ actions.register_abort_handler(async function abort_sync_github() {
31791
31840
  actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
31792
- handle_exit(17);
31793
- };
31841
+ handle_exit();
31842
+ return 17;
31843
+ });
31794
31844
  let DEFAULT_PR_BODY = "";
31795
31845
  if (state.pr_template_body) {
31796
31846
  DEFAULT_PR_BODY = state.pr_template_body;
@@ -31859,6 +31909,7 @@ async function run$3(args) {
31859
31909
  update_pr_body_tasks.push(task);
31860
31910
  }
31861
31911
  await Promise.all(update_pr_body_tasks);
31912
+ actions.unregister_abort_handler();
31862
31913
  actions.set((state) => {
31863
31914
  state.step = "post-rebase-status";
31864
31915
  });
@@ -31871,7 +31922,8 @@ async function run$3(args) {
31871
31922
  if (!argv.verbose) {
31872
31923
  actions.error("Try again with `--verbose` to see more information.");
31873
31924
  }
31874
- await handle_exit(18);
31925
+ handle_exit();
31926
+ actions.exit(18);
31875
31927
  }
31876
31928
  function get_push_group_list() {
31877
31929
  // start from HEAD and work backward to rebase_group_index
@@ -31980,7 +32032,7 @@ async function run$3(args) {
31980
32032
  });
31981
32033
  }
31982
32034
  }
31983
- function handle_exit(code) {
32035
+ function handle_exit() {
31984
32036
  actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restoring PR state\u2026"));
31985
32037
  for (const group of push_group_list) {
31986
32038
  // we may temporarily mark PR as a draft before editing it
@@ -31996,7 +32048,6 @@ async function run$3(args) {
31996
32048
  }
31997
32049
  }
31998
32050
  actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restored PR state."));
31999
- actions.exit(code);
32000
32051
  }
32001
32052
  }
32002
32053
  const get_group_url = (group) => group.pr?.url || group.id;
@@ -32102,7 +32153,7 @@ function RebaseCheck(props) {
32102
32153
  }
32103
32154
 
32104
32155
  function VerboseDebugInfo(props) {
32105
- const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Logging verbose debug information\u2026"));
32156
+ const fallback = reactExports.createElement(Text, { color: colors.yellow }, "Logging verbose debug information\u2026");
32106
32157
  return (reactExports.createElement(Await, { fallback: fallback, function: run$2 }, props.children));
32107
32158
  }
32108
32159
  async function run$2() {
@@ -32165,8 +32216,7 @@ async function run$1() {
32165
32216
  // Calculate commit SHA based on the relative commit number
32166
32217
  const adjusted_number = Number(relative_number) - 1;
32167
32218
  // get the commit SHA of the target commit
32168
- const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`))
32169
- .stdout;
32219
+ const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
32170
32220
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "\uD83D\uDEE0\uFE0F fixup {relative_number} {commit_sha}", values: {
32171
32221
  commit_sha: reactExports.createElement(Parens, null, commit_sha),
32172
32222
  relative_number: relative_number,
@@ -32179,8 +32229,9 @@ async function run$1() {
32179
32229
  });
32180
32230
  if (diff_cmd.code) {
32181
32231
  save_stash = true;
32182
- await cli("git stash -q");
32183
- actions.output(reactExports.createElement(Text, null, "\uD83D\uDCE6 Changes saved to stash"));
32232
+ await cli("git stash --include-untracked");
32233
+ actions.output(reactExports.createElement(Text, { color: colors.yellow },
32234
+ reactExports.createElement(FormatText, { message: "\uD83D\uDCE6 Changes saved to stash" })));
32184
32235
  }
32185
32236
  try {
32186
32237
  // rebase target needs to account for new commit created above
@@ -32199,7 +32250,7 @@ async function run$1() {
32199
32250
  }
32200
32251
  finally {
32201
32252
  if (save_stash) {
32202
- await cli("git stash pop -q");
32253
+ await cli("git stash pop");
32203
32254
  actions.output(reactExports.createElement(Text, { color: colors.green }, "\u2705 Changes restored from stash"));
32204
32255
  }
32205
32256
  }
@@ -32233,13 +32284,7 @@ async function run(args) {
32233
32284
  // commit subject - 80 characters wide, truncated
32234
32285
  const subject_format = `%<(60,trunc)%s`;
32235
32286
  // combine all the above formats into one
32236
- const format = [
32237
- sha_format,
32238
- date_format,
32239
- author_format,
32240
- decoration_format,
32241
- subject_format,
32242
- ].join(" ");
32287
+ const format = [sha_format, date_format, author_format, decoration_format, subject_format].join(" ");
32243
32288
  // view the SHA, description and history graph of last 20 commits
32244
32289
  const rest_args = process_argv.slice(3).join(" ");
32245
32290
  const command = [
@@ -32251,6 +32296,58 @@ async function run(args) {
32251
32296
  actions.output(result.stdout);
32252
32297
  }
32253
32298
 
32299
+ /* eslint-disable no-console */
32300
+ class ErrorBoundary extends reactExports.Component {
32301
+ constructor(props) {
32302
+ super(props);
32303
+ this.state = {
32304
+ error: null,
32305
+ component_stack: "",
32306
+ };
32307
+ }
32308
+ static getDerivedStateFromError(error) {
32309
+ return { error };
32310
+ }
32311
+ componentDidCatch(_error, error_info) {
32312
+ let component_stack = error_info.componentStack;
32313
+ if (component_stack) {
32314
+ // remove first line of component_stack
32315
+ component_stack = component_stack.split("\n").slice(1).join("\n");
32316
+ this.setState({ component_stack });
32317
+ }
32318
+ }
32319
+ render() {
32320
+ if (!this.state.error) {
32321
+ return this.props.children;
32322
+ }
32323
+ const message = this.state.error.message;
32324
+ return (reactExports.createElement(Box, { flexDirection: "column", gap: 0 },
32325
+ reactExports.createElement(Text, { color: colors.red },
32326
+ reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Unhandled error {message}", values: {
32327
+ message: reactExports.createElement(Text, { color: colors.gray }, message),
32328
+ } })),
32329
+ this._render_verbose()));
32330
+ }
32331
+ _render_verbose() {
32332
+ const store_state = Store.getState();
32333
+ if (store_state.argv.verbose) {
32334
+ return reactExports.createElement(Text, { color: colors.gray }, this.state.component_stack);
32335
+ }
32336
+ return (reactExports.createElement(Text, { color: colors.gray },
32337
+ reactExports.createElement(FormatText, { message: "Try again with `--verbose` to see more information." })));
32338
+ }
32339
+ }
32340
+
32341
+ function ExitingGate(props) {
32342
+ const is_exiting = Store.useState((state) => state.is_exiting);
32343
+ if (!is_exiting) {
32344
+ return props.children;
32345
+ }
32346
+ return (reactExports.createElement(Box, { flexDirection: "column" },
32347
+ reactExports.createElement(Text, { color: colors.red },
32348
+ reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Exiting\u2026" }))));
32349
+ }
32350
+
32254
32351
  function App() {
32255
32352
  const actions = Store.useActions();
32256
32353
  const ink = Store.useState((state) => state.ink);
@@ -32267,18 +32364,21 @@ function App() {
32267
32364
  // </React.Fragment>
32268
32365
  // );
32269
32366
  return (reactExports.createElement(Providers, null,
32270
- reactExports.createElement(Debug, null),
32271
- reactExports.createElement(Output, null),
32272
- reactExports.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose || argv.update, timeoutMs: argv.update ? 30 * 1000 : 2 * 1000, onOutput: actions.output, onDone: () => {
32273
- if (argv.update) {
32274
- actions.exit(0);
32275
- }
32276
- } },
32277
- reactExports.createElement(VerboseDebugInfo, null,
32278
- reactExports.createElement(DependencyCheck, null,
32279
- reactExports.createElement(RebaseCheck, null,
32280
- reactExports.createElement(CherryPickCheck, null,
32281
- reactExports.createElement(MaybeMain, null))))))));
32367
+ reactExports.createElement(ErrorBoundary, null,
32368
+ reactExports.createElement(Debug, null),
32369
+ reactExports.createElement(Output, null),
32370
+ reactExports.createElement(ExitingGate, null,
32371
+ reactExports.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose || argv.update, timeoutMs: argv.update ? 30 * 1000 : 2 * 1000, onOutput: actions.output, onDone: () => {
32372
+ if (argv.update) {
32373
+ actions.exit(0);
32374
+ }
32375
+ } },
32376
+ reactExports.createElement(VerboseDebugInfo, null,
32377
+ reactExports.createElement(DependencyCheck, null,
32378
+ reactExports.createElement(RebaseCheck, null,
32379
+ reactExports.createElement(CherryPickCheck, null,
32380
+ reactExports.createElement(MaybeMain, null)))))),
32381
+ reactExports.createElement(HandleCtrlCSigint, null)))));
32282
32382
  }
32283
32383
  function MaybeMain() {
32284
32384
  const argv = Store.useState((state) => state.argv);
@@ -32294,12 +32394,13 @@ function MaybeMain() {
32294
32394
  reactExports.createElement(LocalCommitStatus, null,
32295
32395
  reactExports.createElement(Rebase, null))));
32296
32396
  }
32297
- return (reactExports.createElement(DirtyCheck, null,
32397
+ return (reactExports.createElement(reactExports.Fragment, null,
32298
32398
  !argv.verbose ? null : reactExports.createElement(GithubApiError, null),
32299
32399
  reactExports.createElement(GatherMetadata, null,
32300
- reactExports.createElement(LocalCommitStatus, null,
32301
- reactExports.createElement(DetectInitialPR, null,
32302
- reactExports.createElement(Main, null))))));
32400
+ reactExports.createElement(DirtyCheck, null,
32401
+ reactExports.createElement(LocalCommitStatus, null,
32402
+ reactExports.createElement(DetectInitialPR, null,
32403
+ reactExports.createElement(Main, null)))))));
32303
32404
  }
32304
32405
 
32305
32406
  const align = {
@@ -37620,10 +37721,9 @@ async function command() {
37620
37721
  .wrap(123)
37621
37722
  // disallow unknown options
37622
37723
  .strict()
37623
- .version("1.14.0" )
37724
+ .version("1.15.1" )
37624
37725
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
37625
- .help("help", "Show usage via `git stack help`")
37626
- .argv;
37726
+ .help("help", "Show usage via `git stack help`").argv;
37627
37727
  }
37628
37728
  const GlobalOptions = {
37629
37729
  verbose: {
@@ -37708,22 +37808,52 @@ const FixupOptions = {
37708
37808
  },
37709
37809
  };
37710
37810
 
37711
- command()
37712
- .then((argv) => {
37713
- const ink = render(reactExports.createElement(App, null), {
37714
- // If true, each update will be rendered as a separate output, without replacing the previous one.
37715
- // debug: true,
37716
- });
37717
- Store.setState((state) => {
37718
- state.ink = ink;
37719
- state.process_argv = process.argv;
37720
- state.argv = argv;
37721
- state.cwd = process.cwd();
37722
- });
37723
- Store.getState().actions.debug(JSON.stringify(argv, null, 2));
37724
- })
37725
- // eslint-disable-next-line no-console
37726
- .catch(console.error);
37811
+ /* eslint-disable no-console */
37812
+ (async function main() {
37813
+ try {
37814
+ const argv = await command();
37815
+ process.on("uncaughtException", (error) => {
37816
+ console.error("🚨 uncaughtException");
37817
+ console.error(error);
37818
+ maybe_verbose_help();
37819
+ process.exit(237);
37820
+ });
37821
+ process.on("unhandledRejection", (reason, _promise) => {
37822
+ console.error("🚨 unhandledRejection");
37823
+ console.error(reason);
37824
+ maybe_verbose_help();
37825
+ process.exit(238);
37826
+ });
37827
+ const ink = render(reactExports.createElement(App, null), {
37828
+ // If true, each update will be rendered as a separate output, without replacing the previous one.
37829
+ // debug: true,
37830
+ //
37831
+ // Configure whether Ink should listen to Ctrl+C keyboard input and exit the app.
37832
+ // We intentionally handle this ourselves in `<Exit />`
37833
+ exitOnCtrlC: false,
37834
+ });
37835
+ Store.setState((state) => {
37836
+ state.ink = ink;
37837
+ state.process_argv = process.argv;
37838
+ state.argv = argv;
37839
+ state.cwd = process.cwd();
37840
+ });
37841
+ Store.getState().actions.debug(pretty_json(argv));
37842
+ await ink.waitUntilExit();
37843
+ function maybe_verbose_help() {
37844
+ if (!argv.verbose) {
37845
+ console.error();
37846
+ console.error("Try again with `--verbose` to see more information.");
37847
+ }
37848
+ }
37849
+ }
37850
+ catch (err) {
37851
+ console.error(err);
37852
+ process.exit(236);
37853
+ }
37854
+ })().catch((err) => {
37855
+ console.error(err);
37856
+ });
37727
37857
 
37728
37858
  // prettier-ignore
37729
37859
  const METADATA = {