git-stack-cli 1.15.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 +150 -83
  3. package/package.json +2 -1
  4. package/scripts/link.ts +14 -0
  5. package/src/app/App.tsx +39 -31
  6. package/src/app/AutoUpdate.tsx +9 -24
  7. package/src/app/CherryPickCheck.tsx +1 -2
  8. package/src/app/Debug.tsx +3 -5
  9. package/src/app/DependencyCheck.tsx +6 -6
  10. package/src/app/DetectInitialPR.tsx +2 -8
  11. package/src/app/DirtyCheck.tsx +15 -1
  12. package/src/app/Exit.tsx +1 -5
  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 +1 -12
  17. package/src/app/LocalCommitStatus.tsx +1 -3
  18. package/src/app/LogTimestamp.tsx +1 -5
  19. package/src/app/ManualRebase.tsx +4 -8
  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 +5 -1
  28. package/src/app/SyncGithub.tsx +4 -16
  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 +15 -22
  35. package/src/commands/Log.tsx +3 -7
  36. package/src/commands/Rebase.tsx +6 -8
  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 +2 -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 +7 -13
  47. package/src/core/pretty_json.ts +1 -6
  48. package/src/github/gh.auth_status.test.ts +2 -6
  49. package/src/index.tsx +24 -3
package/README.md CHANGED
@@ -129,12 +129,10 @@ Ensure `node --version` is the same across both projects you are using to test t
129
129
  git submodule update --init --recursive
130
130
  npm i
131
131
  npm run dev
132
- npm unlink git-stack-cli
133
- npm link
132
+ npm run link
134
133
 
135
134
  # navigate to project to test within
136
- npm unlink git-stack-cli
137
- npm link git-stack-cli
135
+ npm unlink git-stack-cli && npm link git-stack-cli
138
136
 
139
137
  git stack --verbose
140
138
  ```
@@ -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)),
@@ -26220,7 +26229,7 @@ function friendlyDateTime(dateTimeish) {
26220
26229
  }
26221
26230
 
26222
26231
  function LogTimestamp() {
26223
- 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] "));
26224
26233
  }
26225
26234
 
26226
26235
  function pretty_json(input) {
@@ -26246,6 +26255,7 @@ const BaseStore = createStore()(immer((set, get) => ({
26246
26255
  sync_github: null,
26247
26256
  is_dirty_check_stash: false,
26248
26257
  abort_handler: null,
26258
+ is_exiting: false,
26249
26259
  step: "loading",
26250
26260
  output: [],
26251
26261
  pending_output: {},
@@ -26253,6 +26263,7 @@ const BaseStore = createStore()(immer((set, get) => ({
26253
26263
  actions: {
26254
26264
  exit(code, clear = true) {
26255
26265
  set((state) => {
26266
+ state.is_exiting = true;
26256
26267
  const node = reactExports.createElement(Exit, { clear: clear, code: code });
26257
26268
  state.mutate.output(state, { node });
26258
26269
  });
@@ -26634,9 +26645,7 @@ function AutoUpdate(props) {
26634
26645
  if (props_ref.current.verbose) {
26635
26646
  handle_output(reactExports.createElement(Text, { key: "init" }, "Checking for latest version..."));
26636
26647
  }
26637
- const timeout_ms = is_finite_value(props.timeoutMs)
26638
- ? props.timeoutMs
26639
- : 2 * 1000;
26648
+ const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
26640
26649
  const npm_json = await Promise.race([
26641
26650
  fetch_json(`https://registry.npmjs.org/${props.name}`),
26642
26651
  sleep(timeout_ms).then(() => {
@@ -26697,8 +26706,8 @@ function AutoUpdate(props) {
26697
26706
  case "prompt":
26698
26707
  return (reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(Text, { color: colors.yellow }, "New version available, would you like to update?"), onYes: async () => {
26699
26708
  handle_output(reactExports.createElement(FormatText, { key: "install", wrapper: reactExports.createElement(Text, null), message: "Installing {name}@{version}...", values: {
26700
- name: (reactExports.createElement(Text, { color: colors.yellow }, props.name)),
26701
- 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),
26702
26711
  } }));
26703
26712
  patch({ status: "install" });
26704
26713
  await cli(`npm install -g ${props.name}@latest`);
@@ -29863,9 +29872,7 @@ async function pr_draft(args) {
29863
29872
  // https://cli.github.com/manual/gh_api
29864
29873
  // https://docs.github.com/en/graphql/reference/mutations#convertpullrequesttodraft
29865
29874
  // https://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview
29866
- const mutation_name = args.draft
29867
- ? "convertPullRequestToDraft"
29868
- : "markPullRequestReadyForReview";
29875
+ const mutation_name = args.draft ? "convertPullRequestToDraft" : "markPullRequestReadyForReview";
29869
29876
  let query = `
29870
29877
  mutation($id: ID!) {
29871
29878
  ${mutation_name}(input: { pullRequestId: $id }) {
@@ -29884,9 +29891,7 @@ async function pr_draft(args) {
29884
29891
  const state = Store.getState();
29885
29892
  const cache_pr = state.pr[args.branch];
29886
29893
  invariant(cache_pr, "cache_pr must exist");
29887
- const command_parts = [
29888
- `gh api graphql -F id="${cache_pr.id}" -f query='${query}'`,
29889
- ];
29894
+ const command_parts = [`gh api graphql -F id="${cache_pr.id}" -f query='${query}'`];
29890
29895
  const command = command_parts.join(" ");
29891
29896
  const cli_result = await cli(command);
29892
29897
  if (cli_result.code !== 0) {
@@ -30346,6 +30351,8 @@ function DirtyCheck(props) {
30346
30351
  switch (state.status) {
30347
30352
  case "done":
30348
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" }));
30349
30356
  case "prompt":
30350
30357
  return (reactExports.createElement(Box, { flexDirection: "column" },
30351
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: {
@@ -30355,8 +30362,10 @@ function DirtyCheck(props) {
30355
30362
  reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git_stash} changes to proceed?", values: {
30356
30363
  git_stash: reactExports.createElement(Command, null, "git stash"),
30357
30364
  } }), onYes: async () => {
30365
+ patch({ status: "stash" });
30358
30366
  await cli("git stash --include-untracked");
30359
- actions.output(reactExports.createElement(Text, null, "\uD83D\uDCE6 Changes saved to stash"));
30367
+ actions.output(reactExports.createElement(Text, { color: colors.yellow },
30368
+ reactExports.createElement(FormatText, { message: "\uD83D\uDCE6 Changes saved to stash" })));
30360
30369
  actions.set((state) => {
30361
30370
  state.is_dirty_check_stash = true;
30362
30371
  });
@@ -30394,7 +30403,7 @@ function DirtyCheck(props) {
30394
30403
  }
30395
30404
 
30396
30405
  function GatherMetadata(props) {
30397
- 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");
30398
30407
  return (reactExports.createElement(Await, { fallback: fallback, function: run$a }, props.children));
30399
30408
  }
30400
30409
  async function run$a() {
@@ -30436,8 +30445,7 @@ async function run$a() {
30436
30445
  return;
30437
30446
  }
30438
30447
  const head = (await cli("git rev-parse HEAD")).stdout;
30439
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
30440
- .stdout;
30448
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
30441
30449
  // handle when there are no detected changes
30442
30450
  if (head === merge_base) {
30443
30451
  actions.newline();
@@ -30531,7 +30539,6 @@ async function run$9() {
30531
30539
 
30532
30540
  function HandleCtrlCSigint() {
30533
30541
  const actions = Store.useActions();
30534
- const [exiting, set_exiting] = reactExports.useState(false);
30535
30542
  useInput((input, key) => {
30536
30543
  handle_input().catch((err) => {
30537
30544
  // eslint-disable-next-line no-console
@@ -30542,22 +30549,17 @@ function HandleCtrlCSigint() {
30542
30549
  actions.clear();
30543
30550
  actions.output(reactExports.createElement(Text, { color: colors.red },
30544
30551
  reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Ctrl+C detected" })));
30545
- set_exiting(true);
30546
30552
  await sleep(1);
30547
30553
  actions.exit(235);
30548
30554
  }
30549
30555
  }
30550
30556
  });
30551
- if (exiting) {
30552
- return (reactExports.createElement(Text, { color: colors.red },
30553
- reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Exiting\u2026" })));
30554
- }
30555
30557
  return null;
30556
30558
  }
30557
30559
 
30558
30560
  function LocalCommitStatus(props) {
30559
30561
  const argv = Store.useState((state) => state.argv);
30560
- 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");
30561
30563
  if (argv["mock-metadata"]) {
30562
30564
  return (reactExports.createElement(Await, { fallback: fallback, function: mock_metadata }, props.children));
30563
30565
  }
@@ -30797,8 +30799,7 @@ async function run$7() {
30797
30799
  const temp_branch_name = `${branch_name}_${short_id()}`;
30798
30800
  try {
30799
30801
  // get latest merge_base relative to local master
30800
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
30801
- .stdout;
30802
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
30802
30803
  // immediately paint all commit to preserve selected commit ranges
30803
30804
  let commit_range = await range(commit_map);
30804
30805
  // reverse group list to ensure we create git revise in correct order
@@ -31094,6 +31095,7 @@ async function run$6() {
31094
31095
  });
31095
31096
  actions.output(reactExports.createElement(StatusTable, null));
31096
31097
  actions.output(reactExports.createElement(Text, null, "\u2705 Everything up to date."));
31098
+ actions.exit(0);
31097
31099
  }
31098
31100
 
31099
31101
  function PreLocalMergeRebase() {
@@ -31154,7 +31156,7 @@ async function run$5() {
31154
31156
  state.pr_templates = pr_templates;
31155
31157
  if (pr_templates.length > 0) {
31156
31158
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{count} queryable templates found under {dir}, but not supported.", values: {
31157
- count: (reactExports.createElement(Text, { color: colors.blue }, pr_templates.length)),
31159
+ count: reactExports.createElement(Text, { color: colors.blue }, pr_templates.length),
31158
31160
  dir: reactExports.createElement(Brackets, null, PR_TEMPLATE.TemplateDir("")),
31159
31161
  } }));
31160
31162
  }
@@ -31816,7 +31818,7 @@ const RE = {
31816
31818
  };
31817
31819
 
31818
31820
  function SyncGithub() {
31819
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: run$3 }));
31821
+ return reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: run$3 });
31820
31822
  }
31821
31823
  async function run$3() {
31822
31824
  const state = Store.getState();
@@ -32151,7 +32153,7 @@ function RebaseCheck(props) {
32151
32153
  }
32152
32154
 
32153
32155
  function VerboseDebugInfo(props) {
32154
- 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");
32155
32157
  return (reactExports.createElement(Await, { fallback: fallback, function: run$2 }, props.children));
32156
32158
  }
32157
32159
  async function run$2() {
@@ -32214,8 +32216,7 @@ async function run$1() {
32214
32216
  // Calculate commit SHA based on the relative commit number
32215
32217
  const adjusted_number = Number(relative_number) - 1;
32216
32218
  // get the commit SHA of the target commit
32217
- const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`))
32218
- .stdout;
32219
+ const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
32219
32220
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "\uD83D\uDEE0\uFE0F fixup {relative_number} {commit_sha}", values: {
32220
32221
  commit_sha: reactExports.createElement(Parens, null, commit_sha),
32221
32222
  relative_number: relative_number,
@@ -32229,7 +32230,8 @@ async function run$1() {
32229
32230
  if (diff_cmd.code) {
32230
32231
  save_stash = true;
32231
32232
  await cli("git stash --include-untracked");
32232
- actions.output(reactExports.createElement(Text, null, "\uD83D\uDCE6 Changes saved to stash"));
32233
+ actions.output(reactExports.createElement(Text, { color: colors.yellow },
32234
+ reactExports.createElement(FormatText, { message: "\uD83D\uDCE6 Changes saved to stash" })));
32233
32235
  }
32234
32236
  try {
32235
32237
  // rebase target needs to account for new commit created above
@@ -32282,13 +32284,7 @@ async function run(args) {
32282
32284
  // commit subject - 80 characters wide, truncated
32283
32285
  const subject_format = `%<(60,trunc)%s`;
32284
32286
  // combine all the above formats into one
32285
- const format = [
32286
- sha_format,
32287
- date_format,
32288
- author_format,
32289
- decoration_format,
32290
- subject_format,
32291
- ].join(" ");
32287
+ const format = [sha_format, date_format, author_format, decoration_format, subject_format].join(" ");
32292
32288
  // view the SHA, description and history graph of last 20 commits
32293
32289
  const rest_args = process_argv.slice(3).join(" ");
32294
32290
  const command = [
@@ -32300,6 +32296,58 @@ async function run(args) {
32300
32296
  actions.output(result.stdout);
32301
32297
  }
32302
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
+
32303
32351
  function App() {
32304
32352
  const actions = Store.useActions();
32305
32353
  const ink = Store.useState((state) => state.ink);
@@ -32316,19 +32364,21 @@ function App() {
32316
32364
  // </React.Fragment>
32317
32365
  // );
32318
32366
  return (reactExports.createElement(Providers, null,
32319
- reactExports.createElement(Debug, null),
32320
- reactExports.createElement(Output, null),
32321
- reactExports.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose || argv.update, timeoutMs: argv.update ? 30 * 1000 : 2 * 1000, onOutput: actions.output, onDone: () => {
32322
- if (argv.update) {
32323
- actions.exit(0);
32324
- }
32325
- } },
32326
- reactExports.createElement(VerboseDebugInfo, null,
32327
- reactExports.createElement(DependencyCheck, null,
32328
- reactExports.createElement(RebaseCheck, null,
32329
- reactExports.createElement(CherryPickCheck, null,
32330
- reactExports.createElement(MaybeMain, null)))))),
32331
- reactExports.createElement(HandleCtrlCSigint, 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)))));
32332
32382
  }
32333
32383
  function MaybeMain() {
32334
32384
  const argv = Store.useState((state) => state.argv);
@@ -32344,12 +32394,13 @@ function MaybeMain() {
32344
32394
  reactExports.createElement(LocalCommitStatus, null,
32345
32395
  reactExports.createElement(Rebase, null))));
32346
32396
  }
32347
- return (reactExports.createElement(DirtyCheck, null,
32397
+ return (reactExports.createElement(reactExports.Fragment, null,
32348
32398
  !argv.verbose ? null : reactExports.createElement(GithubApiError, null),
32349
32399
  reactExports.createElement(GatherMetadata, null,
32350
- reactExports.createElement(LocalCommitStatus, null,
32351
- reactExports.createElement(DetectInitialPR, null,
32352
- reactExports.createElement(Main, null))))));
32400
+ reactExports.createElement(DirtyCheck, null,
32401
+ reactExports.createElement(LocalCommitStatus, null,
32402
+ reactExports.createElement(DetectInitialPR, null,
32403
+ reactExports.createElement(Main, null)))))));
32353
32404
  }
32354
32405
 
32355
32406
  const align = {
@@ -37670,10 +37721,9 @@ async function command() {
37670
37721
  .wrap(123)
37671
37722
  // disallow unknown options
37672
37723
  .strict()
37673
- .version("1.15.0" )
37724
+ .version("1.15.1" )
37674
37725
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
37675
- .help("help", "Show usage via `git stack help`")
37676
- .argv;
37726
+ .help("help", "Show usage via `git stack help`").argv;
37677
37727
  }
37678
37728
  const GlobalOptions = {
37679
37729
  verbose: {
@@ -37758,9 +37808,22 @@ const FixupOptions = {
37758
37808
  },
37759
37809
  };
37760
37810
 
37811
+ /* eslint-disable no-console */
37761
37812
  (async function main() {
37762
37813
  try {
37763
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
+ });
37764
37827
  const ink = render(reactExports.createElement(App, null), {
37765
37828
  // If true, each update will be rendered as a separate output, without replacing the previous one.
37766
37829
  // debug: true,
@@ -37777,14 +37840,18 @@ const FixupOptions = {
37777
37840
  });
37778
37841
  Store.getState().actions.debug(pretty_json(argv));
37779
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
+ }
37780
37849
  }
37781
37850
  catch (err) {
37782
- // eslint-disable-next-line no-console
37783
37851
  console.error(err);
37784
- process.exit(235);
37852
+ process.exit(236);
37785
37853
  }
37786
37854
  })().catch((err) => {
37787
- // eslint-disable-next-line no-console
37788
37855
  console.error(err);
37789
37856
  });
37790
37857
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.15.0",
3
+ "version": "1.15.1",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@
24
24
  "dev": "npm run build -- --watch",
25
25
  "build": "rollup -c rollup.config.js",
26
26
  "build:standalone": "GIT_STACK_STANDALONE=true bun run scripts/build-standalone.ts",
27
+ "link": "bun run scripts/link.ts",
27
28
  "release:npm": "bun run scripts/release-npm.ts",
28
29
  "release:github": "bun run scripts/release-github.ts",
29
30
  "release:brew": "bun run scripts/release-brew.ts",
@@ -0,0 +1,14 @@
1
+ import path from "node:path";
2
+
3
+ import { spawn } from "~/core/spawn";
4
+
5
+ const SCRIPT_DIR = import.meta.dir;
6
+ const PROJECT_DIR = path.join(SCRIPT_DIR, "..");
7
+
8
+ process.chdir(PROJECT_DIR);
9
+
10
+ await spawn.sync("npm unlink git-stack-cli");
11
+ await spawn.sync("npm link");
12
+
13
+ console.debug();
14
+ console.debug("✅", "linked");
package/src/app/App.tsx CHANGED
@@ -19,6 +19,8 @@ import { VerboseDebugInfo } from "~/app/VerboseDebugInfo";
19
19
  import { Fixup } from "~/commands/Fixup";
20
20
  import { Log } from "~/commands/Log";
21
21
  import { Rebase } from "~/commands/Rebase";
22
+ import { ErrorBoundary } from "~/components/ErrorBoundary";
23
+ import { ExitingGate } from "~/components/ExitingGate";
22
24
 
23
25
  export function App() {
24
26
  const actions = Store.useActions();
@@ -42,32 +44,36 @@ export function App() {
42
44
 
43
45
  return (
44
46
  <Providers>
45
- <Debug />
46
- <Output />
47
+ <ErrorBoundary>
48
+ <Debug />
49
+ <Output />
47
50
 
48
- <AutoUpdate
49
- name="git-stack-cli"
50
- verbose={argv.verbose || argv.update}
51
- timeoutMs={argv.update ? 30 * 1000 : 2 * 1000}
52
- onOutput={actions.output}
53
- onDone={() => {
54
- if (argv.update) {
55
- actions.exit(0);
56
- }
57
- }}
58
- >
59
- <VerboseDebugInfo>
60
- <DependencyCheck>
61
- <RebaseCheck>
62
- <CherryPickCheck>
63
- <MaybeMain />
64
- </CherryPickCheck>
65
- </RebaseCheck>
66
- </DependencyCheck>
67
- </VerboseDebugInfo>
68
- </AutoUpdate>
51
+ <ExitingGate>
52
+ <AutoUpdate
53
+ name="git-stack-cli"
54
+ verbose={argv.verbose || argv.update}
55
+ timeoutMs={argv.update ? 30 * 1000 : 2 * 1000}
56
+ onOutput={actions.output}
57
+ onDone={() => {
58
+ if (argv.update) {
59
+ actions.exit(0);
60
+ }
61
+ }}
62
+ >
63
+ <VerboseDebugInfo>
64
+ <DependencyCheck>
65
+ <RebaseCheck>
66
+ <CherryPickCheck>
67
+ <MaybeMain />
68
+ </CherryPickCheck>
69
+ </RebaseCheck>
70
+ </DependencyCheck>
71
+ </VerboseDebugInfo>
72
+ </AutoUpdate>
69
73
 
70
- <HandleCtrlCSigint />
74
+ <HandleCtrlCSigint />
75
+ </ExitingGate>
76
+ </ErrorBoundary>
71
77
  </Providers>
72
78
  );
73
79
  }
@@ -91,16 +97,18 @@ function MaybeMain() {
91
97
  }
92
98
 
93
99
  return (
94
- <DirtyCheck>
100
+ <React.Fragment>
95
101
  {!argv.verbose ? null : <GithubApiError />}
96
102
 
97
103
  <GatherMetadata>
98
- <LocalCommitStatus>
99
- <DetectInitialPR>
100
- <Main />
101
- </DetectInitialPR>
102
- </LocalCommitStatus>
104
+ <DirtyCheck>
105
+ <LocalCommitStatus>
106
+ <DetectInitialPR>
107
+ <Main />
108
+ </DetectInitialPR>
109
+ </LocalCommitStatus>
110
+ </DirtyCheck>
103
111
  </GatherMetadata>
104
- </DirtyCheck>
112
+ </React.Fragment>
105
113
  );
106
114
  }
@@ -67,14 +67,10 @@ export function AutoUpdate(props: Props) {
67
67
 
68
68
  async function auto_update() {
69
69
  if (props_ref.current.verbose) {
70
- handle_output(
71
- <Ink.Text key="init">Checking for latest version...</Ink.Text>
72
- );
70
+ handle_output(<Ink.Text key="init">Checking for latest version...</Ink.Text>);
73
71
  }
74
72
 
75
- const timeout_ms = is_finite_value(props.timeoutMs)
76
- ? props.timeoutMs
77
- : 2 * 1000;
73
+ const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
78
74
 
79
75
  const npm_json = await Promise.race([
80
76
  fetch_json(`https://registry.npmjs.org/${props.name}`),
@@ -94,12 +90,7 @@ export function AutoUpdate(props: Props) {
94
90
  const script_dir = path.dirname(script_path);
95
91
 
96
92
  // dist/ts/index.js
97
- const package_json_path = path.join(
98
- script_dir,
99
- "..",
100
- "..",
101
- "package.json"
102
- );
93
+ const package_json_path = path.join(script_dir, "..", "..", "package.json");
103
94
 
104
95
  type PackageJson = { version: string };
105
96
  const package_json = await read_json<PackageJson>(package_json_path);
@@ -121,7 +112,7 @@ export function AutoUpdate(props: Props) {
121
112
  latest_version: <Brackets>{latest_version}</Brackets>,
122
113
  local_version: <Brackets>{local_version}</Brackets>,
123
114
  }}
124
- />
115
+ />,
125
116
  );
126
117
  }
127
118
 
@@ -134,7 +125,7 @@ export function AutoUpdate(props: Props) {
134
125
  if (semver_result === -1) {
135
126
  // latest version is less than or equal to local version, skip auto update
136
127
  throw new Error(
137
- `latest version < local_version, skipping auto update [${latest_version} < ${local_version}]`
128
+ `latest version < local_version, skipping auto update [${latest_version} < ${local_version}]`,
138
129
  );
139
130
  }
140
131
 
@@ -156,7 +147,7 @@ export function AutoUpdate(props: Props) {
156
147
  handle_output(
157
148
  <Ink.Text key="error" color={colors.red}>
158
149
  {error?.message}
159
- </Ink.Text>
150
+ </Ink.Text>,
160
151
  );
161
152
  }
162
153
  })
@@ -185,16 +176,10 @@ export function AutoUpdate(props: Props) {
185
176
  wrapper={<Ink.Text />}
186
177
  message="Installing {name}@{version}..."
187
178
  values={{
188
- name: (
189
- <Ink.Text color={colors.yellow}>{props.name}</Ink.Text>
190
- ),
191
- version: (
192
- <Ink.Text color={colors.blue}>
193
- {state.latest_version}
194
- </Ink.Text>
195
- ),
179
+ name: <Ink.Text color={colors.yellow}>{props.name}</Ink.Text>,
180
+ version: <Ink.Text color={colors.blue}>{state.latest_version}</Ink.Text>,
196
181
  }}
197
- />
182
+ />,
198
183
  );
199
184
 
200
185
  patch({ status: "install" });