git-stack-cli 1.6.2 → 1.8.0

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.
@@ -17694,7 +17694,7 @@ function YesNoPrompt(props) {
17694
17694
  n));
17695
17695
  }
17696
17696
  return (reactExports.createElement(Box, { flexDirection: "column" },
17697
- reactExports.createElement(Box, null,
17697
+ reactExports.createElement(Box, { alignItems: "flex-end" },
17698
17698
  typeof props.message === "object" ? (props.message) : (reactExports.createElement(Text, { color: colors.yellow }, props.message)),
17699
17699
  reactExports.createElement(Text, null, " "),
17700
17700
  reactExports.createElement(Parens, null,
@@ -26209,6 +26209,8 @@ const BaseStore = createStore()(immer((set, get) => ({
26209
26209
  branch_name: null,
26210
26210
  commit_range: null,
26211
26211
  commit_map: null,
26212
+ pr_templates: [],
26213
+ pr_template_body: null,
26212
26214
  step: "loading",
26213
26215
  output: [],
26214
26216
  pending_output: {},
@@ -27040,10 +27042,15 @@ function DirtyCheck(props) {
27040
27042
  case "done":
27041
27043
  return props.children;
27042
27044
  case "prompt":
27043
- return (reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git} repo is dirty, changed may be lost during {git_stack}, are you sure you wannt to proceed?", values: {
27044
- git: reactExports.createElement(Command, null, "git"),
27045
- git_stack: reactExports.createElement(Command, null, "git stack"),
27046
- } }), onYes: async () => {
27045
+ return (reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(Box, { flexDirection: "column" },
27046
+ reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git} repo has uncommitted changes.", values: {
27047
+ git: reactExports.createElement(Command, null, "git"),
27048
+ git_stack: reactExports.createElement(Command, null, "git stack"),
27049
+ } }),
27050
+ 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: {
27051
+ git: reactExports.createElement(Command, null, "git"),
27052
+ git_stack: reactExports.createElement(Command, null, "git stack"),
27053
+ } })), onYes: async () => {
27047
27054
  patch({ status: "done" });
27048
27055
  }, onNo: async () => {
27049
27056
  actions.exit(0);
@@ -27171,9 +27178,9 @@ function format_time(date) {
27171
27178
  }
27172
27179
 
27173
27180
  function GithubApiError() {
27174
- return reactExports.createElement(Await, { fallback: null, function: run$4 });
27181
+ return reactExports.createElement(Await, { fallback: null, function: run$5 });
27175
27182
  }
27176
- async function run$4() {
27183
+ async function run$5() {
27177
27184
  const actions = Store.getState().actions;
27178
27185
  const res = await cli(`gh api https://api.github.com/rate_limit`);
27179
27186
  const res_json = JSON.parse(res.stdout);
@@ -27340,7 +27347,11 @@ async function pr_status(branch) {
27340
27347
  }
27341
27348
  async function pr_create(args) {
27342
27349
  const title = safe_quote(args.title);
27343
- const cli_result = await cli(`gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`);
27350
+ let command = `gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`;
27351
+ if (args.draft) {
27352
+ command += " --draft";
27353
+ }
27354
+ const cli_result = await cli(command);
27344
27355
  if (cli_result.code !== 0) {
27345
27356
  handle_error(cli_result.output);
27346
27357
  return null;
@@ -27630,9 +27641,9 @@ function encode(value) {
27630
27641
  }
27631
27642
 
27632
27643
  function LocalMergeRebase() {
27633
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$3 }));
27644
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$4 }));
27634
27645
  }
27635
- async function run$3() {
27646
+ async function run$4() {
27636
27647
  const state = Store.getState();
27637
27648
  const actions = state.actions;
27638
27649
  const branch_name = state.branch_name;
@@ -27922,9 +27933,9 @@ const RE = {
27922
27933
  };
27923
27934
 
27924
27935
  function ManualRebase() {
27925
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$2 }));
27936
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$3 }));
27926
27937
  }
27927
- async function run$2() {
27938
+ async function run$3() {
27928
27939
  const state = Store.getState();
27929
27940
  const actions = state.actions;
27930
27941
  const argv = state.argv;
@@ -27938,6 +27949,10 @@ async function run$2() {
27938
27949
  invariant(repo_root, "repo_root must exist");
27939
27950
  // always listen for SIGINT event and restore git state
27940
27951
  process.once("SIGINT", handle_exit);
27952
+ let DEFAULT_PR_BODY = "";
27953
+ if (state.pr_template_body) {
27954
+ DEFAULT_PR_BODY = state.pr_template_body;
27955
+ }
27941
27956
  const temp_branch_name = `${branch_name}_${short_id()}`;
27942
27957
  const commit_range = await range(commit_map);
27943
27958
  // reverse commit list so that we can cherry-pick in order
@@ -28163,7 +28178,8 @@ echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
28163
28178
  branch: group.id,
28164
28179
  base: group.base,
28165
28180
  title: group.title,
28166
- body: "",
28181
+ body: DEFAULT_PR_BODY,
28182
+ draft: argv.draft,
28167
28183
  });
28168
28184
  if (!pr_url) {
28169
28185
  throw new Error("unable to create pr");
@@ -28190,7 +28206,7 @@ echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
28190
28206
  // use the updated pr_url_list to get the actual selected_url
28191
28207
  const selected_url = pr_url_list[i];
28192
28208
  invariant(group.base, "group.base must exist");
28193
- const body = group.pr?.body || "";
28209
+ const body = group.pr?.body || DEFAULT_PR_BODY;
28194
28210
  const update_body = write({
28195
28211
  body,
28196
28212
  pr_url_list,
@@ -28430,9 +28446,9 @@ function get_status_bold(row) {
28430
28446
  }
28431
28447
 
28432
28448
  function PostRebaseStatus() {
28433
- return reactExports.createElement(Await, { fallback: null, function: run$1 });
28449
+ return reactExports.createElement(Await, { fallback: null, function: run$2 });
28434
28450
  }
28435
- async function run$1() {
28451
+ async function run$2() {
28436
28452
  const actions = Store.getState().actions;
28437
28453
  // reset github pr cache before refreshing via commit range below
28438
28454
  actions.reset_pr();
@@ -28461,6 +28477,65 @@ function PreLocalMergeRebase() {
28461
28477
  }, onNo: () => actions.exit(0) }));
28462
28478
  }
28463
28479
 
28480
+ function PreManualRebase() {
28481
+ return reactExports.createElement(Await, { fallback: null, function: run$1 });
28482
+ }
28483
+ async function run$1() {
28484
+ const state = Store.getState();
28485
+ const actions = state.actions;
28486
+ const repo_root = state.repo_root;
28487
+ const argv = state.argv;
28488
+ invariant(repo_root, "repo_root must exist");
28489
+ invariant(argv, "argv must exist");
28490
+ if (!argv.template) {
28491
+ return actions.set((state) => {
28492
+ state.step = "manual-rebase";
28493
+ });
28494
+ }
28495
+ let pr_template_body = null;
28496
+ // look for pull request template
28497
+ // https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
28498
+ // ./.github/pull_request_template.md
28499
+ // ./pull_request_template.md
28500
+ // ./docs/pull_request_template.md
28501
+ for (const key of PR_TEMPLATE_KEY_LIST) {
28502
+ const pr_template_fn = PR_TEMPLATE[key];
28503
+ if (fs.existsSync(pr_template_fn(repo_root))) {
28504
+ pr_template_body = fs.readFileSync(pr_template_fn(repo_root), "utf-8");
28505
+ actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "Using PR template {pr_filepath}", values: {
28506
+ pr_filepath: reactExports.createElement(Brackets, null, pr_template_fn("")),
28507
+ } }));
28508
+ break;
28509
+ }
28510
+ }
28511
+ // ./.github/PULL_REQUEST_TEMPLATE/*.md
28512
+ let pr_templates = [];
28513
+ if (fs.existsSync(PR_TEMPLATE.TemplateDir(repo_root))) {
28514
+ pr_templates = fs.readdirSync(PR_TEMPLATE.TemplateDir(repo_root));
28515
+ }
28516
+ // check if repo has multiple pr templates
28517
+ actions.set((state) => {
28518
+ state.pr_template_body = pr_template_body;
28519
+ state.pr_templates = pr_templates;
28520
+ if (pr_templates.length > 0) {
28521
+ actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{count} queryable templates found under {dir}, but not supported.", values: {
28522
+ count: (reactExports.createElement(Text, { color: colors.blue }, pr_templates.length)),
28523
+ dir: reactExports.createElement(Brackets, null, PR_TEMPLATE.TemplateDir("")),
28524
+ } }));
28525
+ }
28526
+ state.step = "manual-rebase";
28527
+ });
28528
+ }
28529
+ // prettier-ignore
28530
+ const PR_TEMPLATE = Object.freeze({
28531
+ Github: (root) => path.join(root, ".github", "pull_request_template.md"),
28532
+ Root: (root) => path.join(root, "pull_request_template.md"),
28533
+ Docs: (root) => path.join(root, "docs", "pull_request_template.md"),
28534
+ TemplateDir: (root) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE"),
28535
+ });
28536
+ // prettier-ignore
28537
+ const PR_TEMPLATE_KEY_LIST = Object.keys(PR_TEMPLATE);
28538
+
28464
28539
  function PreSelectCommitRanges() {
28465
28540
  const actions = Store.useActions();
28466
28541
  const argv = Store.useState((state) => state.argv);
@@ -28776,7 +28851,7 @@ function SelectCommitRangesInternal(props) {
28776
28851
  }
28777
28852
  switch (inputLower) {
28778
28853
  case "s":
28779
- state.step = "manual-rebase";
28854
+ state.step = "pre-manual-rebase";
28780
28855
  break;
28781
28856
  }
28782
28857
  });
@@ -28975,6 +29050,8 @@ function Main() {
28975
29050
  return reactExports.createElement(PreSelectCommitRanges, null);
28976
29051
  case "select-commit-ranges":
28977
29052
  return reactExports.createElement(SelectCommitRanges, null);
29053
+ case "pre-manual-rebase":
29054
+ return reactExports.createElement(PreManualRebase, null);
28978
29055
  case "manual-rebase":
28979
29056
  return reactExports.createElement(ManualRebase, null);
28980
29057
  case "post-rebase-status":
@@ -34443,12 +34520,23 @@ async function command() {
34443
34520
  type: "string",
34444
34521
  alias: ["b"],
34445
34522
  description: 'Set the master branch name, defaults to "master" (or "main" if "master" is not found)',
34523
+ })
34524
+ .option("draft", {
34525
+ type: "boolean",
34526
+ alias: ["d"],
34527
+ default: false,
34528
+ description: "Open all PRs as drafts",
34446
34529
  })
34447
34530
  .option("write-state-json", {
34448
34531
  hidden: true,
34449
34532
  type: "boolean",
34450
34533
  default: false,
34451
34534
  description: "Write state to local json file for debugging",
34535
+ })
34536
+ .option("template", {
34537
+ type: "boolean",
34538
+ default: true,
34539
+ description: "Use automatic Github PR template, e.g. .github/pull_request_template.md, disable with --no-template",
34452
34540
  })
34453
34541
  .option("mock-metadata", {
34454
34542
  hidden: true,
@@ -34462,7 +34550,7 @@ async function command() {
34462
34550
  .wrap(123)
34463
34551
  // disallow unknown options
34464
34552
  .strict()
34465
- .version("1.6.2" )
34553
+ .version("1.8.0" )
34466
34554
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
34467
34555
  .help("help", "Show usage via `git stack help`").argv);
34468
34556
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.6.2",
3
+ "version": "1.8.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -37,14 +37,24 @@ export function DirtyCheck(props: Props) {
37
37
  return (
38
38
  <YesNoPrompt
39
39
  message={
40
- <FormatText
41
- wrapper={<Ink.Text color={colors.yellow} />}
42
- message="{git} repo is dirty, changed may be lost during {git_stack}, are you sure you wannt to proceed?"
43
- values={{
44
- git: <Command>git</Command>,
45
- git_stack: <Command>git stack</Command>,
46
- }}
47
- />
40
+ <Ink.Box flexDirection="column">
41
+ <FormatText
42
+ wrapper={<Ink.Text color={colors.yellow} />}
43
+ message="{git} repo has uncommitted changes."
44
+ values={{
45
+ git: <Command>git</Command>,
46
+ git_stack: <Command>git stack</Command>,
47
+ }}
48
+ />
49
+ <FormatText
50
+ wrapper={<Ink.Text color={colors.yellow} />}
51
+ message="Changes may be lost during {git_stack}, are you sure you want to proceed?"
52
+ values={{
53
+ git: <Command>git</Command>,
54
+ git_stack: <Command>git stack</Command>,
55
+ }}
56
+ />
57
+ </Ink.Box>
48
58
  }
49
59
  onYes={async () => {
50
60
  patch({ status: "done" });
package/src/app/Main.tsx CHANGED
@@ -5,6 +5,7 @@ import { LocalMergeRebase } from "~/app/LocalMergeRebase";
5
5
  import { ManualRebase } from "~/app/ManualRebase";
6
6
  import { PostRebaseStatus } from "~/app/PostRebaseStatus";
7
7
  import { PreLocalMergeRebase } from "~/app/PreLocalMergeRebase";
8
+ import { PreManualRebase } from "~/app/PreManualRebase";
8
9
  import { PreSelectCommitRanges } from "~/app/PreSelectCommitRanges";
9
10
  import { SelectCommitRanges } from "~/app/SelectCommitRanges";
10
11
  import { Status } from "~/app/Status";
@@ -36,6 +37,9 @@ export function Main() {
36
37
  case "select-commit-ranges":
37
38
  return <SelectCommitRanges />;
38
39
 
40
+ case "pre-manual-rebase":
41
+ return <PreManualRebase />;
42
+
39
43
  case "manual-rebase":
40
44
  return <ManualRebase />;
41
45
 
@@ -46,6 +46,11 @@ async function run() {
46
46
  // always listen for SIGINT event and restore git state
47
47
  process.once("SIGINT", handle_exit);
48
48
 
49
+ let DEFAULT_PR_BODY = "";
50
+ if (state.pr_template_body) {
51
+ DEFAULT_PR_BODY = state.pr_template_body;
52
+ }
53
+
49
54
  const temp_branch_name = `${branch_name}_${short_id()}`;
50
55
 
51
56
  const commit_range = await CommitMetadata.range(commit_map);
@@ -328,7 +333,8 @@ async function run() {
328
333
  branch: group.id,
329
334
  base: group.base,
330
335
  title: group.title,
331
- body: "",
336
+ body: DEFAULT_PR_BODY,
337
+ draft: argv.draft,
332
338
  });
333
339
 
334
340
  if (!pr_url) {
@@ -363,7 +369,7 @@ async function run() {
363
369
 
364
370
  invariant(group.base, "group.base must exist");
365
371
 
366
- const body = group.pr?.body || "";
372
+ const body = group.pr?.body || DEFAULT_PR_BODY;
367
373
 
368
374
  const update_body = StackSummaryTable.write({
369
375
  body,
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import * as Ink from "ink-cjs";
7
+
8
+ import { Await } from "~/app/Await";
9
+ import { Brackets } from "~/app/Brackets";
10
+ import { FormatText } from "~/app/FormatText";
11
+ import { Store } from "~/app/Store";
12
+ import { colors } from "~/core/colors";
13
+ import { invariant } from "~/core/invariant";
14
+
15
+ export function PreManualRebase() {
16
+ return <Await fallback={null} function={run} />;
17
+ }
18
+
19
+ async function run() {
20
+ const state = Store.getState();
21
+ const actions = state.actions;
22
+ const repo_root = state.repo_root;
23
+ const argv = state.argv;
24
+
25
+ invariant(repo_root, "repo_root must exist");
26
+ invariant(argv, "argv must exist");
27
+
28
+ if (!argv.template) {
29
+ return actions.set((state) => {
30
+ state.step = "manual-rebase";
31
+ });
32
+ }
33
+
34
+ let pr_template_body: null | string = null;
35
+
36
+ // look for pull request template
37
+ // https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
38
+ // ./.github/pull_request_template.md
39
+ // ./pull_request_template.md
40
+ // ./docs/pull_request_template.md
41
+ for (const key of PR_TEMPLATE_KEY_LIST) {
42
+ const pr_template_fn = PR_TEMPLATE[key as keyof typeof PR_TEMPLATE];
43
+
44
+ if (fs.existsSync(pr_template_fn(repo_root))) {
45
+ pr_template_body = fs.readFileSync(pr_template_fn(repo_root), "utf-8");
46
+
47
+ actions.output(
48
+ <FormatText
49
+ wrapper={<Ink.Text color={colors.yellow} />}
50
+ message="Using PR template {pr_filepath}"
51
+ values={{
52
+ pr_filepath: <Brackets>{pr_template_fn("")}</Brackets>,
53
+ }}
54
+ />
55
+ );
56
+
57
+ break;
58
+ }
59
+ }
60
+
61
+ // ./.github/PULL_REQUEST_TEMPLATE/*.md
62
+ let pr_templates: Array<string> = [];
63
+ if (fs.existsSync(PR_TEMPLATE.TemplateDir(repo_root))) {
64
+ pr_templates = fs.readdirSync(PR_TEMPLATE.TemplateDir(repo_root));
65
+ }
66
+
67
+ // check if repo has multiple pr templates
68
+ actions.set((state) => {
69
+ state.pr_template_body = pr_template_body;
70
+ state.pr_templates = pr_templates;
71
+
72
+ if (pr_templates.length > 0) {
73
+ actions.output(
74
+ <FormatText
75
+ wrapper={<Ink.Text color={colors.yellow} />}
76
+ message="{count} queryable templates found under {dir}, but not supported."
77
+ values={{
78
+ count: (
79
+ <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>
80
+ ),
81
+ dir: <Brackets>{PR_TEMPLATE.TemplateDir("")}</Brackets>,
82
+ }}
83
+ />
84
+ );
85
+ }
86
+
87
+ state.step = "manual-rebase";
88
+ });
89
+ }
90
+
91
+ // prettier-ignore
92
+ const PR_TEMPLATE = Object.freeze({
93
+ Github: (root: string) => path.join(root, ".github", "pull_request_template.md"),
94
+ Root: (root: string) => path.join(root, "pull_request_template.md"),
95
+ Docs: (root: string) => path.join(root, "docs", "pull_request_template.md"),
96
+ TemplateDir: (root: string) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE"),
97
+ });
98
+
99
+ // prettier-ignore
100
+ const PR_TEMPLATE_KEY_LIST = Object.keys(PR_TEMPLATE) as Array<keyof typeof PR_TEMPLATE>;
@@ -143,7 +143,7 @@ function SelectCommitRangesInternal(props: Props) {
143
143
 
144
144
  switch (inputLower) {
145
145
  case "s":
146
- state.step = "manual-rebase";
146
+ state.step = "pre-manual-rebase";
147
147
  break;
148
148
  }
149
149
  });
package/src/app/Store.tsx CHANGED
@@ -38,6 +38,8 @@ export type State = {
38
38
  branch_name: null | string;
39
39
  commit_range: null | CommitMetadata.CommitRange;
40
40
  commit_map: null | CommitMap;
41
+ pr_templates: Array<string>;
42
+ pr_template_body: null | string;
41
43
 
42
44
  step:
43
45
  | "github-api-error"
@@ -47,6 +49,7 @@ export type State = {
47
49
  | "local-merge-rebase"
48
50
  | "pre-select-commit-ranges"
49
51
  | "select-commit-ranges"
52
+ | "pre-manual-rebase"
50
53
  | "manual-rebase"
51
54
  | "post-rebase-status";
52
55
 
@@ -98,6 +101,8 @@ const BaseStore = createStore<State>()(
98
101
  branch_name: null,
99
102
  commit_range: null,
100
103
  commit_map: null,
104
+ pr_templates: [],
105
+ pr_template_body: null,
101
106
 
102
107
  step: "loading",
103
108
 
@@ -55,7 +55,7 @@ export function YesNoPrompt(props: Props) {
55
55
 
56
56
  return (
57
57
  <Ink.Box flexDirection="column">
58
- <Ink.Box>
58
+ <Ink.Box alignItems="flex-end">
59
59
  {typeof props.message === "object" ? (
60
60
  props.message
61
61
  ) : (
package/src/command.ts CHANGED
@@ -71,6 +71,13 @@ export async function command() {
71
71
  'Set the master branch name, defaults to "master" (or "main" if "master" is not found)',
72
72
  })
73
73
 
74
+ .option("draft", {
75
+ type: "boolean",
76
+ alias: ["d"],
77
+ default: false,
78
+ description: "Open all PRs as drafts",
79
+ })
80
+
74
81
  .option("write-state-json", {
75
82
  hidden: true,
76
83
  type: "boolean",
@@ -78,6 +85,13 @@ export async function command() {
78
85
  description: "Write state to local json file for debugging",
79
86
  })
80
87
 
88
+ .option("template", {
89
+ type: "boolean",
90
+ default: true,
91
+ description:
92
+ "Use automatic Github PR template, e.g. .github/pull_request_template.md, disable with --no-template",
93
+ })
94
+
81
95
  .option("mock-metadata", {
82
96
  hidden: true,
83
97
  type: "boolean",
@@ -131,14 +131,18 @@ type CreatePullRequestArgs = {
131
131
  base: string;
132
132
  title: string;
133
133
  body: string;
134
+ draft: boolean;
134
135
  };
135
136
 
136
137
  export async function pr_create(args: CreatePullRequestArgs) {
137
138
  const title = safe_quote(args.title);
139
+ let command = `gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`;
138
140
 
139
- const cli_result = await cli(
140
- `gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`
141
- );
141
+ if (args.draft) {
142
+ command += " --draft";
143
+ }
144
+
145
+ const cli_result = await cli(command);
142
146
 
143
147
  if (cli_result.code !== 0) {
144
148
  handle_error(cli_result.output);