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
@@ -15,35 +15,12 @@ import { invariant } from "~/core/invariant";
15
15
  import { short_id } from "~/core/short_id";
16
16
 
17
17
  export function ManualRebase() {
18
- const abort_handler = React.useRef(() => {});
19
-
20
- React.useEffect(function listen_sigint() {
21
- process.once("SIGINT", sigint_handler);
22
-
23
- return function cleanup() {
24
- process.removeListener("SIGINT", sigint_handler);
25
- };
26
-
27
- async function sigint_handler() {
28
- abort_handler.current();
29
- }
30
- }, []);
31
-
32
18
  return (
33
- <Await
34
- fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
35
- function={async function () {
36
- await run({ abort_handler });
37
- }}
38
- />
19
+ <Await fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>} function={run} />
39
20
  );
40
21
  }
41
22
 
42
- type Args = {
43
- abort_handler: React.MutableRefObject<() => void>;
44
- };
45
-
46
- async function run(args: Args) {
23
+ async function run() {
47
24
  const state = Store.getState();
48
25
  const actions = state.actions;
49
26
  const argv = state.argv;
@@ -57,18 +34,18 @@ async function run(args: Args) {
57
34
  invariant(commit_map, "commit_map must exist");
58
35
  invariant(repo_root, "repo_root must exist");
59
36
 
60
- // always listen for SIGINT event and restore git state
61
- args.abort_handler.current = function sigint_handler() {
37
+ // immediately register abort_handler in case of ctrl+c exit
38
+ actions.register_abort_handler(async function abort_manual_rebase() {
62
39
  actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
63
- handle_exit(15);
64
- };
40
+ handle_exit();
41
+ return 15;
42
+ });
65
43
 
66
44
  const temp_branch_name = `${branch_name}_${short_id()}`;
67
45
 
68
46
  try {
69
47
  // get latest merge_base relative to local master
70
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
71
- .stdout;
48
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
72
49
 
73
50
  // immediately paint all commit to preserve selected commit ranges
74
51
  let commit_range = await CommitMetadata.range(commit_map);
@@ -137,6 +114,8 @@ async function run(args: Args) {
137
114
 
138
115
  restore_git();
139
116
 
117
+ actions.unregister_abort_handler();
118
+
140
119
  if (argv.sync) {
141
120
  actions.set((state) => {
142
121
  state.step = "sync-github";
@@ -157,7 +136,8 @@ async function run(args: Args) {
157
136
  actions.error("Try again with `--verbose` to see more information.");
158
137
  }
159
138
 
160
- handle_exit(16);
139
+ handle_exit();
140
+ actions.exit(16);
161
141
  }
162
142
 
163
143
  // cleanup git operations if cancelled during manual rebase
@@ -185,11 +165,11 @@ async function run(args: Args) {
185
165
  cli.sync(`pwd`, spawn_options);
186
166
  }
187
167
 
188
- function handle_exit(code: number) {
168
+ function handle_exit() {
189
169
  actions.output(
190
170
  <Ink.Text color={colors.yellow}>
191
171
  Restoring <Brackets>{branch_name}</Brackets>…
192
- </Ink.Text>
172
+ </Ink.Text>,
193
173
  );
194
174
 
195
175
  restore_git();
@@ -197,9 +177,7 @@ async function run(args: Args) {
197
177
  actions.output(
198
178
  <Ink.Text color={colors.yellow}>
199
179
  Restored <Brackets>{branch_name}</Brackets>.
200
- </Ink.Text>
180
+ </Ink.Text>,
201
181
  );
202
-
203
- actions.exit(code);
204
182
  }
205
183
  }
@@ -51,7 +51,7 @@ export function MultiSelect<T>(props: Props<T>) {
51
51
  });
52
52
 
53
53
  return set;
54
- }
54
+ },
55
55
  );
56
56
 
57
57
  // clamp index to keep in item range
@@ -81,7 +81,7 @@ export function MultiSelect<T>(props: Props<T>) {
81
81
  }
82
82
 
83
83
  return 0;
84
- }
84
+ },
85
85
  );
86
86
 
87
87
  const selectRef = React.useRef(false);
@@ -26,4 +26,6 @@ async function run() {
26
26
  actions.output(<StatusTable />);
27
27
 
28
28
  actions.output(<Ink.Text>✅ Everything up to date.</Ink.Text>);
29
+
30
+ actions.exit(0);
29
31
  }
@@ -51,7 +51,7 @@ async function run() {
51
51
  values={{
52
52
  pr_filepath: <Brackets>{pr_template_fn("")}</Brackets>,
53
53
  }}
54
- />
54
+ />,
55
55
  );
56
56
 
57
57
  break;
@@ -75,12 +75,10 @@ async function run() {
75
75
  wrapper={<Ink.Text color={colors.yellow} />}
76
76
  message="{count} queryable templates found under {dir}, but not supported."
77
77
  values={{
78
- count: (
79
- <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>
80
- ),
78
+ count: <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>,
81
79
  dir: <Brackets>{PR_TEMPLATE.TemplateDir("")}</Brackets>,
82
80
  }}
83
- />
81
+ />,
84
82
  );
85
83
  }
86
84
 
@@ -40,8 +40,7 @@ export function RebaseCheck(props: Props) {
40
40
  <YesNoPrompt
41
41
  message={
42
42
  <Ink.Text color={colors.yellow}>
43
- <Command>git rebase</Command> detected, would you like to abort
44
- it?
43
+ <Command>git rebase</Command> detected, would you like to abort it?
45
44
  </Ink.Text>
46
45
  }
47
46
  onYes={async () => {
@@ -37,7 +37,7 @@ function SelectCommitRangesInternal(props: Props) {
37
37
 
38
38
  const [selected_group_id, set_selected_group_id] = React.useState(() => {
39
39
  const first_group = props.commit_range.group_list.find(
40
- (g) => g.id !== props.commit_range.UNASSIGNED
40
+ (g) => g.id !== props.commit_range.UNASSIGNED,
41
41
  );
42
42
 
43
43
  if (first_group) {
@@ -54,14 +54,11 @@ function SelectCommitRangesInternal(props: Props) {
54
54
  const next_group_list = group_list.concat(group);
55
55
  return next_group_list;
56
56
  },
57
- []
57
+ [],
58
58
  );
59
59
 
60
60
  const [commit_map, update_commit_map] = React.useReducer(
61
- (
62
- map: Map<string, null | string>,
63
- args: { key: string; value: null | string }
64
- ) => {
61
+ (map: Map<string, null | string>, args: { key: string; value: null | string }) => {
65
62
  map.set(args.key, args.value);
66
63
 
67
64
  // console.debug("update_commit_map", map, args);
@@ -74,7 +71,7 @@ function SelectCommitRangesInternal(props: Props) {
74
71
  }
75
72
 
76
73
  return new Map(map);
77
- }
74
+ },
78
75
  );
79
76
 
80
77
  const group_list: Array<SimpleGroup> = [];
@@ -88,8 +85,7 @@ function SelectCommitRangesInternal(props: Props) {
88
85
  }
89
86
  }
90
87
 
91
- const total_group_count =
92
- new_group_list.length + props.commit_range.group_list.length;
88
+ const total_group_count = new_group_list.length + props.commit_range.group_list.length;
93
89
 
94
90
  for (let i = 0; i < props.commit_range.group_list.length; i++) {
95
91
  const index = props.commit_range.group_list.length - i - 1;
@@ -411,7 +407,7 @@ function SelectCommitRangesInternal(props: Props) {
411
407
  group: <Brackets>{title}</Brackets>,
412
408
  note: <Parens>{id}</Parens>,
413
409
  }}
414
- />
410
+ />,
415
411
  );
416
412
 
417
413
  // console.debug("submit_group_input", { title, id });
@@ -59,7 +59,7 @@ async function run() {
59
59
  {` --force `}
60
60
  </Ink.Text>
61
61
  <Ink.Text>to force update all pull requests.</Ink.Text>
62
- </Ink.Text>
62
+ </Ink.Text>,
63
63
  );
64
64
 
65
65
  actions.exit(0);
@@ -84,10 +84,7 @@ function StatusColumn(props: TableColumnProps<Row>) {
84
84
  const value = props.row[props.column];
85
85
 
86
86
  return (
87
- <Ink.Text
88
- color={get_status_color(props.row)}
89
- bold={get_status_bold(props.row)}
90
- >
87
+ <Ink.Text color={get_status_color(props.row)} bold={get_status_bold(props.row)}>
91
88
  {get_status_icon(props.row)} {value}
92
89
  </Ink.Text>
93
90
  );
package/src/app/Store.tsx CHANGED
@@ -7,6 +7,7 @@ import { immer } from "zustand/middleware/immer";
7
7
  import { Exit } from "~/app/Exit";
8
8
  import { LogTimestamp } from "~/app/LogTimestamp";
9
9
  import { colors } from "~/core/colors";
10
+ import { pretty_json } from "~/core/pretty_json";
10
11
 
11
12
  import type { Instance as InkInstance } from "ink-cjs";
12
13
  import type { Argv } from "~/command";
@@ -29,6 +30,9 @@ type SyncGithubState = {
29
30
  rebase_group_index: number;
30
31
  };
31
32
 
33
+ // async function that returns exit code
34
+ type AbortHandler = () => Promise<number>;
35
+
32
36
  export type State = {
33
37
  // set immediately in `index.tsx` so no `null` scenario
34
38
  process_argv: Array<string>;
@@ -47,6 +51,9 @@ export type State = {
47
51
  pr_templates: Array<string>;
48
52
  pr_template_body: null | string;
49
53
  sync_github: null | SyncGithubState;
54
+ is_dirty_check_stash: boolean;
55
+ abort_handler: null | AbortHandler;
56
+ is_exiting: boolean;
50
57
 
51
58
  step:
52
59
  | "github-api-error"
@@ -71,7 +78,7 @@ export type State = {
71
78
  clear(): void;
72
79
  unmount(): void;
73
80
  newline(): void;
74
- json(value: object): void;
81
+ json(value: pretty_json.JSONValue): void;
75
82
  error(message: string): void;
76
83
  output(node: React.ReactNode): void;
77
84
  debug(node: React.ReactNode, id?: string): void;
@@ -79,6 +86,8 @@ export type State = {
79
86
  isDebug(): boolean;
80
87
 
81
88
  reset_pr(): void;
89
+ register_abort_handler(abort_handler: AbortHandler): void;
90
+ unregister_abort_handler(): void;
82
91
 
83
92
  set(setter: Setter): void;
84
93
  };
@@ -113,6 +122,9 @@ const BaseStore = createStore<State>()(
113
122
  pr_templates: [],
114
123
  pr_template_body: null,
115
124
  sync_github: null,
125
+ is_dirty_check_stash: false,
126
+ abort_handler: null,
127
+ is_exiting: false,
116
128
 
117
129
  step: "loading",
118
130
 
@@ -124,6 +136,8 @@ const BaseStore = createStore<State>()(
124
136
  actions: {
125
137
  exit(code, clear = true) {
126
138
  set((state) => {
139
+ state.is_exiting = true;
140
+
127
141
  const node = <Exit clear={clear} code={code} />;
128
142
  state.mutate.output(state, { node });
129
143
  });
@@ -146,7 +160,7 @@ const BaseStore = createStore<State>()(
146
160
 
147
161
  json(value) {
148
162
  set((state) => {
149
- const node = JSON.stringify(value, null, 2);
163
+ const node = pretty_json(value);
150
164
  state.mutate.output(state, { node });
151
165
  });
152
166
  },
@@ -189,6 +203,18 @@ const BaseStore = createStore<State>()(
189
203
  });
190
204
  },
191
205
 
206
+ register_abort_handler(abort_handler) {
207
+ set((state) => {
208
+ state.abort_handler = abort_handler;
209
+ });
210
+ },
211
+
212
+ unregister_abort_handler() {
213
+ set((state) => {
214
+ state.abort_handler = null;
215
+ });
216
+ },
217
+
192
218
  set(setter) {
193
219
  set((state) => {
194
220
  setter(state);
@@ -238,7 +264,7 @@ const BaseStore = createStore<State>()(
238
264
  return state.argv?.verbose || false;
239
265
  },
240
266
  },
241
- }))
267
+ })),
242
268
  );
243
269
 
244
270
  function renderOutputArgs(args: MutateOutputArgs) {
@@ -14,35 +14,10 @@ import { invariant } from "~/core/invariant";
14
14
  import type * as CommitMetadata from "~/core/CommitMetadata";
15
15
 
16
16
  export function SyncGithub() {
17
- const abort_handler = React.useRef(() => {});
18
-
19
- React.useEffect(function listen_sigint() {
20
- process.once("SIGINT", sigint_handler);
21
-
22
- return function cleanup() {
23
- process.removeListener("SIGINT", sigint_handler);
24
- };
25
-
26
- function sigint_handler() {
27
- abort_handler.current();
28
- }
29
- }, []);
30
-
31
- return (
32
- <Await
33
- fallback={<Ink.Text color={colors.yellow}>Syncing…</Ink.Text>}
34
- function={async function () {
35
- await run({ abort_handler });
36
- }}
37
- />
38
- );
17
+ return <Await fallback={<Ink.Text color={colors.yellow}>Syncing…</Ink.Text>} function={run} />;
39
18
  }
40
19
 
41
- type Args = {
42
- abort_handler: React.MutableRefObject<() => void>;
43
- };
44
-
45
- async function run(args: Args) {
20
+ async function run() {
46
21
  const state = Store.getState();
47
22
  const actions = state.actions;
48
23
  const argv = state.argv;
@@ -60,11 +35,12 @@ async function run(args: Args) {
60
35
  const commit_range = sync_github.commit_range;
61
36
  const rebase_group_index = sync_github.rebase_group_index;
62
37
 
63
- // always listen for SIGINT event and restore pr state
64
- args.abort_handler.current = function sigint_handler() {
38
+ // immediately register abort_handler in case of ctrl+c exit
39
+ actions.register_abort_handler(async function abort_sync_github() {
65
40
  actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
66
- handle_exit(17);
67
- };
41
+ handle_exit();
42
+ return 17;
43
+ });
68
44
 
69
45
  let DEFAULT_PR_BODY = "";
70
46
  if (state.pr_template_body) {
@@ -151,6 +127,8 @@ async function run(args: Args) {
151
127
 
152
128
  await Promise.all(update_pr_body_tasks);
153
129
 
130
+ actions.unregister_abort_handler();
131
+
154
132
  actions.set((state) => {
155
133
  state.step = "post-rebase-status";
156
134
  });
@@ -164,7 +142,8 @@ async function run(args: Args) {
164
142
  actions.error("Try again with `--verbose` to see more information.");
165
143
  }
166
144
 
167
- await handle_exit(18);
145
+ handle_exit();
146
+ actions.exit(18);
168
147
  }
169
148
 
170
149
  function get_push_group_list() {
@@ -218,10 +197,7 @@ async function run(args: Args) {
218
197
  }
219
198
  }
220
199
 
221
- async function after_push(args: {
222
- group: CommitMetadataGroup;
223
- pr_url_list: Array<string>;
224
- }) {
200
+ async function after_push(args: { group: CommitMetadataGroup; pr_url_list: Array<string> }) {
225
201
  const { group, pr_url_list } = args;
226
202
 
227
203
  invariant(group.base, "group.base must exist");
@@ -305,10 +281,8 @@ async function run(args: Args) {
305
281
  }
306
282
  }
307
283
 
308
- function handle_exit(code: number) {
309
- actions.output(
310
- <Ink.Text color={colors.yellow}>Restoring PR state…</Ink.Text>
311
- );
284
+ function handle_exit() {
285
+ actions.output(<Ink.Text color={colors.yellow}>Restoring PR state…</Ink.Text>);
312
286
 
313
287
  for (const group of push_group_list) {
314
288
  // we may temporarily mark PR as a draft before editing it
@@ -326,11 +300,7 @@ async function run(args: Args) {
326
300
  }
327
301
  }
328
302
 
329
- actions.output(
330
- <Ink.Text color={colors.yellow}>Restored PR state.</Ink.Text>
331
- );
332
-
333
- actions.exit(code);
303
+ actions.output(<Ink.Text color={colors.yellow}>Restored PR state.</Ink.Text>);
334
304
  }
335
305
  }
336
306
 
package/src/app/Table.tsx CHANGED
@@ -70,10 +70,7 @@ export function Table<T extends BaseRow>(props: Props<T>) {
70
70
  remaining_space -= breathing_room;
71
71
 
72
72
  if (props.fillColumn) {
73
- max_col_width[props.fillColumn] = Math.min(
74
- max_col_width[props.fillColumn],
75
- remaining_space
76
- );
73
+ max_col_width[props.fillColumn] = Math.min(max_col_width[props.fillColumn], remaining_space);
77
74
  }
78
75
  }
79
76
 
@@ -91,9 +88,7 @@ export function Table<T extends BaseRow>(props: Props<T>) {
91
88
  width={available_width}
92
89
  >
93
90
  {RowColumnList.map((column) => {
94
- const ColumnComponent = props.columns[
95
- column
96
- ] as ColumnComponent<T>;
91
+ const ColumnComponent = props.columns[column] as ColumnComponent<T>;
97
92
 
98
93
  return (
99
94
  <Ink.Box key={String(column)} width={max_col_width[column]}>
@@ -122,14 +117,9 @@ type BaseRow = Record<string, string | number>;
122
117
 
123
118
  type Column<T extends BaseRow> = keyof T;
124
119
 
125
- type ColumnComponent<T extends BaseRow> = (
126
- props: TableColumnProps<T>
127
- ) => React.ReactNode;
120
+ type ColumnComponent<T extends BaseRow> = (props: TableColumnProps<T>) => React.ReactNode;
128
121
 
129
- type ColumnComponentMap<T extends BaseRow> = Record<
130
- Column<T>,
131
- ColumnComponent<T>
132
- >;
122
+ type ColumnComponentMap<T extends BaseRow> = Record<Column<T>, ColumnComponent<T>>;
133
123
 
134
124
  export type TableColumnProps<T extends BaseRow> = {
135
125
  row: T;
@@ -19,7 +19,7 @@ export function TextInput(props: Props) {
19
19
  function sync_value_prop() {
20
20
  set_value(get_value(props));
21
21
  },
22
- [props.value]
22
+ [props.value],
23
23
  );
24
24
 
25
25
  const [caret_visible, set_caret_visible] = React.useState(false);
@@ -69,12 +69,7 @@ export function TextInput(props: Props) {
69
69
  // console.debug("[TextInput]", { value });
70
70
 
71
71
  return (
72
- <Ink.Box
73
- borderStyle="single"
74
- minHeight={1}
75
- borderColor={colors.yellow}
76
- borderDimColor
77
- >
72
+ <Ink.Box borderStyle="single" minHeight={1} borderColor={colors.yellow} borderDimColor>
78
73
  <Ink.Text>{value || ""}</Ink.Text>
79
74
 
80
75
  <Ink.Text color={colors.yellow} dimColor inverse={caret_visible}>
@@ -12,11 +12,7 @@ type Props = {
12
12
  };
13
13
 
14
14
  export function VerboseDebugInfo(props: Props) {
15
- const fallback = (
16
- <Ink.Text color={colors.yellow}>
17
- Logging verbose debug information…
18
- </Ink.Text>
19
- );
15
+ const fallback = <Ink.Text color={colors.yellow}>Logging verbose debug information…</Ink.Text>;
20
16
 
21
17
  return (
22
18
  <Await fallback={fallback} function={run}>
@@ -2,56 +2,67 @@ import * as React from "react";
2
2
 
3
3
  import * as Ink from "ink-cjs";
4
4
 
5
+ import { FormatText } from "~/app/FormatText";
5
6
  import { Parens } from "~/app/Parens";
6
7
  import { colors } from "~/core/colors";
7
8
 
9
+ type Handler = () => void;
10
+
8
11
  type Props = {
9
12
  message: React.ReactNode;
10
- onYes(): void;
11
- onNo(): void;
13
+ onYes: Handler;
14
+ onNo: Handler;
12
15
  };
13
16
 
14
17
  export function YesNoPrompt(props: Props) {
15
18
  const [answer, set_answer] = React.useState("");
16
19
 
20
+ const answered_ref = React.useRef(false);
21
+
17
22
  Ink.useInput((input) => {
18
- const inputLower = input.toLowerCase();
23
+ // prevent answering multiple times
24
+ if (answered_ref.current) {
25
+ return;
26
+ }
19
27
 
20
- set_answer(inputLower);
28
+ const input_lower = input.toLowerCase();
21
29
 
22
- switch (inputLower) {
30
+ let handler: undefined | Handler;
31
+
32
+ switch (input_lower) {
23
33
  case "n":
24
- return props.onNo();
34
+ handler = props.onNo;
35
+ break;
25
36
 
26
37
  case "y":
27
- return props.onYes();
38
+ handler = props.onYes;
39
+ break;
40
+ }
41
+
42
+ // handler if valid answer (y or n)
43
+ if (handler) {
44
+ answered_ref.current = true;
45
+ set_answer(input_lower);
46
+ handler();
28
47
  }
29
48
  });
30
49
 
31
- // prettier-ignore
32
- const y = <Ink.Text bold color={colors.green}>Y</Ink.Text>;
33
- const n = <Ink.Text color={colors.red}>n</Ink.Text>;
34
-
35
- let choices;
36
-
37
- switch (answer) {
38
- case "y":
39
- choices = y;
40
- break;
41
-
42
- case "n":
43
- choices = n;
44
- break;
45
-
46
- default:
47
- choices = (
48
- <React.Fragment>
49
- {y}
50
- <Ink.Text>/</Ink.Text>
51
- {n}
52
- </React.Fragment>
53
- );
54
- }
50
+ const choices = (function get_choices() {
51
+ // prettier-ignore
52
+ const y = <Ink.Text bold color={colors.green}>Y</Ink.Text>;
53
+ const n = <Ink.Text color={colors.red}>n</Ink.Text>;
54
+
55
+ switch (answer) {
56
+ case "y":
57
+ return y;
58
+
59
+ case "n":
60
+ return n;
61
+
62
+ default:
63
+ return <FormatText message="{y}/{n}" values={{ y, n }} />;
64
+ }
65
+ })();
55
66
 
56
67
  return (
57
68
  <Ink.Box flexDirection="column">
package/src/command.ts CHANGED
@@ -11,26 +11,22 @@ export async function command() {
11
11
  yargs(hideBin(process.argv))
12
12
  .usage("Usage: git stack [command] [options]")
13
13
 
14
- .command("$0", "Sync commit ranges to Github", (yargs) =>
15
- yargs.options(DefaultOptions)
16
- )
14
+ .command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions))
17
15
 
18
- .command(
19
- "fixup [commit]",
20
- "Amend staged changes to a specific commit in history",
21
- (yargs) => yargs.positional("commit", FixupOptions.commit)
16
+ .command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) =>
17
+ yargs.positional("commit", FixupOptions.commit),
22
18
  )
23
19
 
24
20
  .command(
25
21
  "log [args...]",
26
22
  "Print an abbreviated log with numbered commits, useful for git stack fixup",
27
- (yargs) => yargs.strict(false)
23
+ (yargs) => yargs.strict(false),
28
24
  )
29
25
 
30
26
  .command(
31
27
  "rebase",
32
28
  "Update local branch via rebase with latest changes from origin master branch",
33
- (yargs) => yargs
29
+ (yargs) => yargs,
34
30
  )
35
31
 
36
32
  .option("verbose", GlobalOptions.verbose)
@@ -43,12 +39,8 @@ export async function command() {
43
39
  // disallow unknown options
44
40
  .strict()
45
41
  .version(process.env.CLI_VERSION || "unknown")
46
- .showHidden(
47
- "show-hidden",
48
- "Show hidden options via `git stack help --show-hidden`"
49
- )
50
- .help("help", "Show usage via `git stack help`")
51
- .argv as unknown as Promise<Argv>
42
+ .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
43
+ .help("help", "Show usage via `git stack help`").argv as unknown as Promise<Argv>
52
44
  );
53
45
  }
54
46
 
@@ -86,8 +78,7 @@ const DefaultOptions = {
86
78
  "verify": {
87
79
  type: "boolean",
88
80
  default: true,
89
- description:
90
- "Run git hooks such as pre-commit and pre-push, disable with --no-verify",
81
+ description: "Run git hooks such as pre-commit and pre-push, disable with --no-verify",
91
82
  },
92
83
 
93
84
  "update": {