git-stack-cli 0.6.0 → 0.7.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.
package/README.md CHANGED
@@ -16,5 +16,5 @@ git stack
16
16
  npm run dev
17
17
  npm link
18
18
 
19
- git stack --debug
19
+ git stack --verbose
20
20
  ```
@@ -5,40 +5,16 @@ export const METADATA = {
5
5
  "invalid": false,
6
6
  "group_list": [
7
7
  {
8
- "id": "79402548-a996-4c2a-a338-86633040358e",
9
- "pr": {
10
- "baseRefName": "d9fc206e-70ce-4b1c-b950-1f54cf1fe112",
11
- "commits": [
12
- {
13
- "authoredDate": "2023-10-29T21:58:16Z",
14
- "authors": [
15
- {
16
- "email": "noah@iamnoah.com",
17
- "id": "MDQ6VXNlcjI5MDA4NA==",
18
- "login": "magus",
19
- "name": "magus"
20
- }
21
- ],
22
- "committedDate": "2023-11-17T10:43:34Z",
23
- "messageBody": "git-multi-diff-id: 79402548-a996-4c2a-a338-86633040358e",
24
- "messageHeadline": "orange color",
25
- "oid": "3dcc44fbe1f293d0c212e71dfcd459e9d88879a8"
26
- }
27
- ],
28
- "headRefName": "79402548-a996-4c2a-a338-86633040358e",
29
- "number": 32,
30
- "state": "OPEN",
31
- "title": "orange color",
32
- "url": "https://github.com/magus/git-multi-diff-playground/pull/32"
33
- },
34
- "base": "d9fc206e-70ce-4b1c-b950-1f54cf1fe112",
35
- "dirty": false,
8
+ "id": "unassigned",
9
+ "pr": null,
10
+ "base": null,
11
+ "dirty": true,
36
12
  "commits": [
37
13
  {
38
14
  "sha": "3dcc44fbe1f293d0c212e71dfcd459e9d88879a8",
39
15
  "message": "orange color",
40
- "raw_message": "orange color\n\ngit-multi-diff-id: 79402548-a996-4c2a-a338-86633040358e",
41
- "branch_id": "79402548-a996-4c2a-a338-86633040358e"
16
+ "raw_message": "orange color",
17
+ "branch_id": null
42
18
  }
43
19
  ]
44
20
  },
@@ -422,6 +398,7 @@ export const METADATA = {
422
398
  "branch_id": "79402548-a996-4c2a-a338-86633040358e"
423
399
  }
424
400
  ],
401
+ "pr_lookup": {},
425
402
  "UNASSIGNED": "unassigned"
426
403
  },
427
404
  "pr": {
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import * as Ink from "ink";
5
5
  import { cli } from "../core/cli.js";
6
+ import { colors } from "../core/colors.js";
6
7
  import { fetch_json } from "../core/fetch_json.js";
7
8
  import { read_json } from "../core/read_json.js";
8
9
  import { semver_compare } from "../core/semver_compare.js";
@@ -87,7 +88,7 @@ export function AutoUpdate(props) {
87
88
  patch({ status, error, local_version, latest_version });
88
89
  onError(error);
89
90
  if (props_ref.current.verbose) {
90
- handle_output(React.createElement(Ink.Text, { key: "error", dimColor: true, color: "red" }, error?.message));
91
+ handle_output(React.createElement(Ink.Text, { key: "error", dimColor: true, color: colors.red }, error?.message));
91
92
  }
92
93
  });
93
94
  }, []);
@@ -96,10 +97,10 @@ export function AutoUpdate(props) {
96
97
  case "init":
97
98
  return null;
98
99
  case "prompt":
99
- return (React.createElement(YesNoPrompt, { message: React.createElement(Ink.Text, { color: "yellow" }, "New version available, would you like to update?"), onYes: async () => {
100
+ return (React.createElement(YesNoPrompt, { message: React.createElement(Ink.Text, { color: colors.yellow }, "New version available, would you like to update?"), onYes: async () => {
100
101
  handle_output(React.createElement(FormatText, { key: "install", wrapper: React.createElement(Ink.Text, null), message: "Installing {name}@{version}...", values: {
101
- name: React.createElement(Ink.Text, { color: "yellow" }, props.name),
102
- version: (React.createElement(Ink.Text, { color: "#38bdf8" }, state.latest_version)),
102
+ name: (React.createElement(Ink.Text, { color: colors.yellow }, props.name)),
103
+ version: (React.createElement(Ink.Text, { color: colors.blue }, state.latest_version)),
103
104
  } }));
104
105
  patch({ status: "install" });
105
106
  await cli(`npm install -g ${props.name}@latest`);
@@ -1,8 +1,9 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  export function Brackets(props) {
4
- const color = "#f97316";
5
- const text_color = "#38bdf8";
5
+ const color = colors.orange;
6
+ const text_color = colors.blue;
6
7
  return (React.createElement(Ink.Text, { color: text_color },
7
8
  React.createElement(Ink.Text, { color: color }, "["),
8
9
  props.children,
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  export function Command(props) {
4
- const text_color = "#f97316";
5
+ const text_color = colors.orange;
5
6
  return (React.createElement(Ink.Text, { bold: true, color: text_color }, props.children));
6
7
  }
package/dist/app/Debug.js CHANGED
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import * as Ink from "ink";
5
+ import { colors } from "../core/colors.js";
5
6
  import { invariant } from "../core/invariant.js";
6
7
  import * as json from "../core/json.js";
7
8
  import { Store } from "./Store.js";
@@ -12,7 +13,7 @@ export function Debug() {
12
13
  const debug = Store.useState((state) => state.select.debug(state));
13
14
  React.useEffect(function debugMessageOnce() {
14
15
  if (debug) {
15
- actions.output(React.createElement(Ink.Text, { color: "yellow" }, "Debug mode enabled"));
16
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow }, "Debug mode enabled"));
16
17
  }
17
18
  }, [argv]);
18
19
  React.useEffect(function syncStateJson() {
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { cli } from "../core/cli.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { is_command_available } from "../core/is_command_available.js";
5
6
  import { match_group } from "../core/match_group.js";
6
7
  import { semver_compare } from "../core/semver_compare.js";
@@ -11,19 +12,19 @@ import { Store } from "./Store.js";
11
12
  import { Url } from "./Url.js";
12
13
  export function DependencyCheck(props) {
13
14
  const actions = Store.useActions();
14
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
15
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow },
15
16
  "Checking ",
16
17
  React.createElement(Command, null, "git"),
17
18
  " install..."), function: async () => {
18
19
  if (is_command_available("git")) {
19
20
  return;
20
21
  }
21
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
22
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
22
23
  React.createElement(Command, null, "git"),
23
24
  " must be installed."));
24
25
  actions.exit(2);
25
26
  } },
26
- React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
27
+ React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow },
27
28
  "Checking ",
28
29
  React.createElement(Command, null, "node"),
29
30
  " install..."), function: async () => {
@@ -32,12 +33,12 @@ export function DependencyCheck(props) {
32
33
  if (semver_result >= 0) {
33
34
  return;
34
35
  }
35
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
36
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
36
37
  React.createElement(Command, null, "node"),
37
38
  " must be installed."));
38
39
  actions.exit(2);
39
40
  } },
40
- React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
41
+ React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow },
41
42
  React.createElement(Ink.Text, null,
42
43
  "Checking ",
43
44
  React.createElement(Command, null, "gh"),
@@ -45,10 +46,10 @@ export function DependencyCheck(props) {
45
46
  if (is_command_available("gh")) {
46
47
  return;
47
48
  }
48
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
49
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
49
50
  React.createElement(Command, null, "gh"),
50
51
  " must be installed."));
51
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
52
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
52
53
  React.createElement(Ink.Text, null, "Visit "),
53
54
  React.createElement(Url, null, "https://cli.github.com"),
54
55
  React.createElement(Ink.Text, null, " to install the github cli "),
@@ -56,7 +57,7 @@ export function DependencyCheck(props) {
56
57
  React.createElement(Command, null, "gh"))));
57
58
  actions.exit(3);
58
59
  } },
59
- React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
60
+ React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow },
60
61
  React.createElement(Ink.Text, null,
61
62
  "Checking ",
62
63
  React.createElement(Command, null, "gh auth status"),
@@ -71,7 +72,7 @@ export function DependencyCheck(props) {
71
72
  });
72
73
  return;
73
74
  }
74
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
75
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
75
76
  React.createElement(Command, null, "gh"),
76
77
  React.createElement(Ink.Text, null, " requires login, please run "),
77
78
  React.createElement(Command, null, "gh auth login")));
@@ -1,33 +1,60 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { cli } from "../core/cli.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { invariant } from "../core/invariant.js";
5
6
  import { match_group } from "../core/match_group.js";
6
7
  import { Await } from "./Await.js";
8
+ import { Brackets } from "./Brackets.js";
9
+ import { FormatText } from "./FormatText.js";
7
10
  import { Store } from "./Store.js";
8
11
  export function GatherMetadata(props) {
9
12
  const argv = Store.useState((state) => state.argv);
10
13
  invariant(argv, "argv must exist");
11
- const fallback = (React.createElement(Ink.Text, { color: "yellow" }, "Gathering local git information..."));
14
+ const fallback = (React.createElement(Ink.Text, { color: colors.yellow }, "Gathering local git information..."));
12
15
  return (React.createElement(Await, { fallback: fallback, function: gather_metadata }, props.children));
13
16
  }
14
17
  async function gather_metadata() {
15
18
  const actions = Store.getState().actions;
19
+ const argv = Store.getState().argv;
20
+ invariant(argv, "argv must exist");
16
21
  try {
22
+ // default to master branch, fallback to main
23
+ let master_branch;
24
+ if (argv.branch) {
25
+ actions.debug(React.createElement(FormatText, { message: "Setting master branch to {branch}", values: {
26
+ branch: React.createElement(Brackets, null, argv.branch),
27
+ } }));
28
+ master_branch = argv.branch;
29
+ }
30
+ else {
31
+ const detect_master = await cli(`git branch --list "${BRANCH.master}" --color=never`);
32
+ if (detect_master.stdout !== "") {
33
+ master_branch = BRANCH.master;
34
+ }
35
+ else {
36
+ actions.debug(React.createElement(FormatText, { message: "Could not find {master} branch, falling back to {main}", values: {
37
+ master: React.createElement(Brackets, null, BRANCH.master),
38
+ main: React.createElement(Brackets, null, BRANCH.main),
39
+ } }));
40
+ master_branch = BRANCH.main;
41
+ }
42
+ }
17
43
  const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
18
44
  // handle when there are no detected changes
19
- if (branch_name === "master") {
45
+ if (branch_name === master_branch) {
20
46
  actions.newline();
21
47
  actions.error("Must run within a branch.");
22
48
  actions.exit(0);
23
49
  return;
24
50
  }
25
51
  const head = (await cli("git rev-parse HEAD")).stdout;
26
- const merge_base = (await cli("git merge-base HEAD master")).stdout;
52
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
53
+ .stdout;
27
54
  // handle when there are no detected changes
28
55
  if (head === merge_base) {
29
56
  actions.newline();
30
- actions.output(React.createElement(Ink.Text, { color: "gray" }, "No changes detected."));
57
+ actions.output(React.createElement(Ink.Text, { color: colors.gray }, "No changes detected."));
31
58
  actions.exit(0);
32
59
  return;
33
60
  }
@@ -37,6 +64,7 @@ async function gather_metadata() {
37
64
  const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
38
65
  Store.setState((state) => {
39
66
  state.repo_path = repo_path;
67
+ state.master_branch = master_branch;
40
68
  state.head = head;
41
69
  state.merge_base = merge_base;
42
70
  state.branch_name = branch_name;
@@ -49,6 +77,7 @@ async function gather_metadata() {
49
77
  actions.error(err.message);
50
78
  }
51
79
  }
80
+ actions.exit(7);
52
81
  }
53
82
  }
54
83
  const RE = {
@@ -56,3 +85,7 @@ const RE = {
56
85
  // https://github.com/magus/git-multi-diff-playground.git
57
86
  repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
58
87
  };
88
+ const BRANCH = {
89
+ master: "master",
90
+ main: "main",
91
+ };
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { cli } from "../core/cli.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import * as date from "../core/date.js";
5
6
  import { invariant } from "../core/invariant.js";
6
7
  import { Await } from "./Await.js";
@@ -41,9 +42,9 @@ async function run() {
41
42
  React.createElement(Ink.Text, null, "/"),
42
43
  React.createElement(Ink.Text, null, limit)),
43
44
  React.createElement(Ink.Text, null, " will reset at "),
44
- React.createElement(Ink.Text, { bold: true, color: "yellow" }, reset_time),
45
+ React.createElement(Ink.Text, { bold: true, color: colors.yellow }, reset_time),
45
46
  React.createElement(Ink.Text, null, " "),
46
47
  React.createElement(Parens, null,
47
48
  React.createElement(Ink.Text, null, "in "),
48
- React.createElement(Ink.Text, { bold: true, color: "yellow" }, time_until))));
49
+ React.createElement(Ink.Text, { bold: true, color: colors.yellow }, time_until))));
49
50
  }
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import * as CommitMetadata from "../core/CommitMetadata.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { invariant } from "../core/invariant.js";
5
6
  import * as json from "../core/json.js";
6
7
  import { Await } from "./Await.js";
@@ -8,7 +9,7 @@ import { Store } from "./Store.js";
8
9
  export function LocalCommitStatus(props) {
9
10
  const argv = Store.useState((state) => state.argv);
10
11
  invariant(argv, "argv must exist");
11
- const fallback = (React.createElement(Ink.Text, { color: "yellow" }, "Fetching PR status from Github..."));
12
+ const fallback = (React.createElement(Ink.Text, { color: colors.yellow }, "Fetching PR status from Github..."));
12
13
  if (argv["mock-metadata"]) {
13
14
  return (React.createElement(Await, { fallback: fallback, function: mock_metadata }, props.children));
14
15
  }
@@ -3,6 +3,7 @@ import * as Ink from "ink";
3
3
  import * as CommitMetadata from "../core/CommitMetadata.js";
4
4
  import * as Metadata from "../core/Metadata.js";
5
5
  import { cli } from "../core/cli.js";
6
+ import { colors } from "../core/colors.js";
6
7
  import { invariant } from "../core/invariant.js";
7
8
  import { short_id } from "../core/short_id.js";
8
9
  import { Await } from "./Await.js";
@@ -11,32 +12,33 @@ import { FormatText } from "./FormatText.js";
11
12
  import { Parens } from "./Parens.js";
12
13
  import { Store } from "./Store.js";
13
14
  export function LocalMergeRebase() {
14
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: run }));
15
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow }, "Rebasing commits..."), function: run }));
15
16
  }
16
17
  async function run() {
17
18
  const state = Store.getState();
18
19
  const actions = state.actions;
19
20
  const branch_name = state.branch_name;
20
21
  const commit_range = state.commit_range;
22
+ const master_branch = state.master_branch;
21
23
  invariant(branch_name, "branch_name must exist");
22
24
  invariant(commit_range, "commit_range must exist");
23
25
  // always listen for SIGINT event and restore git state
24
26
  process.once("SIGINT", handle_exit);
25
27
  const temp_branch_name = `${branch_name}_${short_id()}`;
26
28
  try {
27
- await cli(`git fetch --no-tags -v origin master:master`);
28
- const master_sha = (await cli(`git rev-parse master`)).stdout;
29
+ await cli(`git fetch --no-tags -v origin ${master_branch}:${master_branch}`);
30
+ const master_sha = (await cli(`git rev-parse ${master_branch}`)).stdout;
29
31
  const rebase_merge_base = master_sha;
30
32
  // create temporary branch based on merge base
31
33
  await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
32
34
  for (let i = 0; i < commit_range.commit_list.length; i++) {
33
35
  const commit = commit_range.commit_list[i];
34
- const commit_pr = commit_range.pr_map.get(commit.branch_id || "");
36
+ const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
35
37
  // drop commits that are in groups of merged PRs
36
38
  const merged_pr = commit_pr?.state === "MERGED";
37
39
  if (merged_pr) {
38
40
  if (actions.isDebug()) {
39
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" }), message: "Dropping {commit_message} {pr_status}", values: {
41
+ actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Dropping {commit_message} {pr_status}", values: {
40
42
  commit_message: React.createElement(Brackets, null, commit.message),
41
43
  pr_status: React.createElement(Parens, null, "MERGED"),
42
44
  } }));
@@ -45,14 +47,14 @@ async function run() {
45
47
  }
46
48
  // cherry-pick and amend commits one by one
47
49
  if (actions.isDebug()) {
48
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" }), message: "Picking {commit_message}", values: {
50
+ actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Picking {commit_message}", values: {
49
51
  commit_message: React.createElement(Brackets, null, commit.message),
50
52
  } }));
51
53
  }
52
54
  await cli(`git cherry-pick ${commit.sha}`);
53
55
  if (commit.branch_id && !commit_pr) {
54
56
  if (actions.isDebug()) {
55
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" }), message: "Cleaning up unused group {group}", values: {
57
+ actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Cleaning up unused group {group}", values: {
56
58
  group: React.createElement(Brackets, null, commit.branch_id),
57
59
  } }));
58
60
  }
@@ -99,15 +101,15 @@ async function run() {
99
101
  }
100
102
  }
101
103
  function handle_exit() {
102
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
104
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
103
105
  "Restoring ",
104
106
  React.createElement(Brackets, null, branch_name),
105
107
  "..."));
106
108
  restore_git();
107
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
109
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
108
110
  "Restored ",
109
111
  React.createElement(Brackets, null, branch_name),
110
112
  "."));
111
- actions.exit(5);
113
+ actions.exit(6);
112
114
  }
113
115
  }
@@ -4,6 +4,7 @@ import * as CommitMetadata from "../core/CommitMetadata.js";
4
4
  import * as Metadata from "../core/Metadata.js";
5
5
  import * as StackSummaryTable from "../core/StackSummaryTable.js";
6
6
  import { cli } from "../core/cli.js";
7
+ import { colors } from "../core/colors.js";
7
8
  import * as github from "../core/github.js";
8
9
  import { invariant } from "../core/invariant.js";
9
10
  import { short_id } from "../core/short_id.js";
@@ -12,7 +13,7 @@ import { Brackets } from "./Brackets.js";
12
13
  import { FormatText } from "./FormatText.js";
13
14
  import { Store } from "./Store.js";
14
15
  export function ManualRebase(props) {
15
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
16
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow }, "Rebasing commits..."), function: () => run(props) }));
16
17
  }
17
18
  async function run(props) {
18
19
  const state = Store.getState();
@@ -49,9 +50,11 @@ async function run(props) {
49
50
  try {
50
51
  // create temporary branch based on merge base
51
52
  await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
53
+ const pr_url_list = commit_range.group_list.map(get_group_url);
52
54
  for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
53
55
  const group = commit_range.group_list[i];
54
56
  invariant(group.base, "group.base must exist");
57
+ const selected_url = get_group_url(group);
55
58
  // cherry-pick and amend commits one by one
56
59
  for (const commit of group.commits) {
57
60
  await cli(`git cherry-pick ${commit.sha}`);
@@ -60,13 +63,13 @@ async function run(props) {
60
63
  await cli(`git commit --amend -m "${new_message}"`);
61
64
  }
62
65
  }
63
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
66
+ actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
64
67
  group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
65
68
  } }));
66
69
  if (!props.skipSync) {
67
70
  // push to origin since github requires commit shas to line up perfectly
68
71
  const git_push_command = [`git push -f origin HEAD:${group.id}`];
69
- if (argv["no-verify"]) {
72
+ if (argv.verify === false) {
70
73
  git_push_command.push("--no-verify");
71
74
  }
72
75
  await cli(git_push_command.join(" "));
@@ -77,8 +80,8 @@ async function run(props) {
77
80
  base: group.base,
78
81
  body: StackSummaryTable.write({
79
82
  body: group.pr.body,
80
- commit_range,
81
- selected_group_id: group.id,
83
+ pr_url_list,
84
+ selected_url,
82
85
  }),
83
86
  });
84
87
  }
@@ -88,21 +91,51 @@ async function run(props) {
88
91
  // move to temporary branch for creating pr
89
92
  await cli(`git checkout -b ${group.id}`);
90
93
  // create pr in github
91
- await github.pr_create({
94
+ const pr_url = await github.pr_create({
92
95
  branch: group.id,
93
96
  base: group.base,
94
97
  title: group.title,
95
- body: StackSummaryTable.write({
96
- body: "",
97
- commit_range,
98
- selected_group_id: group.id,
99
- }),
98
+ body: "",
100
99
  });
100
+ if (!pr_url) {
101
+ throw new Error("unable to create pr");
102
+ }
103
+ // update pr_url_list with created pr_url
104
+ for (let i = 0; i < pr_url_list.length; i++) {
105
+ const url = pr_url_list[i];
106
+ if (url === selected_url) {
107
+ pr_url_list[i] = pr_url;
108
+ }
109
+ }
101
110
  // move back to temp branch
102
111
  await cli(`git checkout ${temp_branch_name}`);
103
112
  }
104
113
  }
105
114
  }
115
+ // finally, ensure all prs have the updated stack table from updated pr_url_list
116
+ for (let i = 0; i < commit_range.group_list.length; i++) {
117
+ const group = commit_range.group_list[i];
118
+ // use the updated pr_url_list to get the actual selected_url
119
+ const selected_url = pr_url_list[i];
120
+ invariant(group.base, "group.base must exist");
121
+ const body = group.pr?.body || "";
122
+ const update_body = StackSummaryTable.write({
123
+ body,
124
+ pr_url_list,
125
+ selected_url,
126
+ });
127
+ if (update_body === body) {
128
+ actions.debug(`Skipping body update for ${selected_url}`);
129
+ }
130
+ else {
131
+ actions.debug(`Update body for ${selected_url}`);
132
+ await github.pr_edit({
133
+ branch: group.id,
134
+ base: group.base,
135
+ body: update_body,
136
+ });
137
+ }
138
+ }
106
139
  // after all commits have been cherry-picked and amended
107
140
  // move the branch pointer to the newly created temporary branch
108
141
  // now we are in locally in sync with github and on the original branch
@@ -139,15 +172,16 @@ async function run(props) {
139
172
  }
140
173
  }
141
174
  function handle_exit() {
142
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
175
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
143
176
  "Restoring ",
144
177
  React.createElement(Brackets, null, branch_name),
145
178
  "..."));
146
179
  restore_git();
147
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
180
+ actions.output(React.createElement(Ink.Text, { color: colors.yellow },
148
181
  "Restored ",
149
182
  React.createElement(Brackets, null, branch_name),
150
183
  "."));
151
184
  actions.exit(5);
152
185
  }
153
186
  }
187
+ const get_group_url = (group) => group.pr?.url || group.id;
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { clamp } from "../core/clamp.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { wrap_index } from "../core/wrap_index.js";
5
6
  export function MultiSelect(props) {
6
7
  const [selected_set, select] = React.useReducer((state, value) => {
@@ -52,12 +53,13 @@ export function MultiSelect(props) {
52
53
  // console.debug({ item, selected, state });
53
54
  props.onSelect({ item, selected, state });
54
55
  }, [selected_set]);
55
- Ink.useInput((_input, key) => {
56
+ Ink.useInput((input, key) => {
56
57
  if (props.disabled) {
57
58
  // console.debug("[MultiSelect] disabled, ignoring input");
58
59
  return;
59
60
  }
60
- if (key.return) {
61
+ const space = input === " ";
62
+ if (key.return || space) {
61
63
  selectRef.current = true;
62
64
  const item = props.items[index];
63
65
  if (!item.disabled) {
@@ -100,7 +102,7 @@ function ItemRow(props) {
100
102
  let underline;
101
103
  let dimColor;
102
104
  if (props.active) {
103
- color = "#38bdf8";
105
+ color = colors.blue;
104
106
  underline = true;
105
107
  }
106
108
  if (props.selected) {
@@ -125,7 +127,7 @@ function Radio(props) {
125
127
  if (props.selected) {
126
128
  // display = "✓";
127
129
  display = "◉";
128
- color = "green";
130
+ color = colors.green;
129
131
  }
130
132
  else {
131
133
  // display = " ";
@@ -133,7 +135,7 @@ function Radio(props) {
133
135
  color = "";
134
136
  }
135
137
  if (props.disabled) {
136
- color = "gray";
138
+ color = colors.gray;
137
139
  dimColor = true;
138
140
  }
139
141
  return (React.createElement(Ink.Text, { bold: props.selected, color: color, dimColor: dimColor }, display));
@@ -1,7 +1,8 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  export function Parens(props) {
4
- const color = "#38bdf8";
5
+ const color = colors.blue;
5
6
  return (React.createElement(Ink.Text, null,
6
7
  React.createElement(Ink.Text, { color: color }, "("),
7
8
  props.children,
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  import { invariant } from "../core/invariant.js";
4
5
  import { short_id } from "../core/short_id.js";
5
6
  import { wrap_index } from "../core/wrap_index.js";
@@ -72,7 +73,7 @@ function SelectCommitRangesInternal(props) {
72
73
  state.commit_map = {};
73
74
  for (const [sha, id] of commit_map.entries()) {
74
75
  if (id) {
75
- const group = new_group_list.find((g) => g.id === id);
76
+ const group = group_list.find((g) => g.id === id);
76
77
  // console.debug({ sha, id, group });
77
78
  if (group) {
78
79
  state.commit_map[sha] = group;
@@ -155,37 +156,37 @@ function SelectCommitRangesInternal(props) {
155
156
  React.createElement(Ink.Text, { wrap: "truncate-end" }, group.title)),
156
157
  React.createElement(Ink.Text, null, right_arrow)),
157
158
  React.createElement(Ink.Box, { height: 1 }),
158
- unassigned_count > 0 ? (React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "{count} unassigned commits, press {c} to {create} a new group", values: {
159
- count: (React.createElement(Ink.Text, { color: "#3b82f6", bold: true }, unassigned_count)),
160
- c: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "c")),
161
- create: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
159
+ unassigned_count > 0 ? (React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.gray }), message: "{count} unassigned commits, press {c} to {create} a new group", values: {
160
+ count: (React.createElement(Ink.Text, { color: colors.yellow, bold: true }, unassigned_count)),
161
+ c: (React.createElement(Ink.Text, { bold: true, color: colors.green }, "c")),
162
+ create: (React.createElement(Ink.Text, { bold: true, color: colors.green },
162
163
  React.createElement(Parens, null, "c"),
163
164
  "reate")),
164
165
  } })) : (React.createElement(React.Fragment, null,
165
166
  React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, null), message: "\uD83C\uDF89 Done! Press {s} to {sync} the commits to Github", values: {
166
- s: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "s")),
167
- sync: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
167
+ s: (React.createElement(Ink.Text, { bold: true, color: colors.green }, "s")),
168
+ sync: (React.createElement(Ink.Text, { bold: true, color: colors.green },
168
169
  React.createElement(Parens, null, "s"),
169
170
  "ync")),
170
171
  } }))),
171
172
  !group_input ? null : (React.createElement(React.Fragment, null,
172
173
  React.createElement(Ink.Box, { height: 1 }),
173
- React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Enter a title for the PR {note}", values: {
174
+ React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.gray }), message: "Enter a title for the PR {note}", values: {
174
175
  note: (React.createElement(Parens, null,
175
176
  React.createElement(FormatText, { message: "press {enter} to submit", values: {
176
- enter: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.enter)),
177
+ enter: (React.createElement(Ink.Text, { bold: true, color: colors.green }, SYMBOL.enter)),
177
178
  } }))),
178
179
  } }),
179
180
  React.createElement(TextInput, { onSubmit: submit_group_input }),
180
181
  React.createElement(Ink.Box, { height: 1 }))),
181
182
  React.createElement(Ink.Box, null,
182
- React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Press {left} and {right} to view PR groups", values: {
183
- left: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.left)),
184
- right: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.right)),
183
+ React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.gray }), message: "Press {left} and {right} to view PR groups", values: {
184
+ left: (React.createElement(Ink.Text, { bold: true, color: colors.green }, SYMBOL.left)),
185
+ right: (React.createElement(Ink.Text, { bold: true, color: colors.green }, SYMBOL.right)),
185
186
  } })),
186
187
  React.createElement(Ink.Box, null,
187
- React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Press {enter} to toggle commit selection", values: {
188
- enter: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.enter)),
188
+ React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.gray }), message: "Press {enter} to toggle commit selection", values: {
189
+ enter: (React.createElement(Ink.Text, { bold: true, color: colors.green }, SYMBOL.enter)),
189
190
  } }))));
190
191
  function submit_group_input(title) {
191
192
  const id = short_id();
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  import { invariant } from "../core/invariant.js";
4
5
  import { Await } from "./Await.js";
5
6
  import { StatusTable } from "./StatusTable.js";
@@ -26,7 +27,7 @@ async function run(args) {
26
27
  }
27
28
  for (let i = 0; i < commit_range.commit_list.length; i++) {
28
29
  const commit = commit_range.commit_list[i];
29
- const commit_pr = commit_range.pr_map.get(commit.branch_id || "");
30
+ const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
30
31
  if (commit.branch_id && !commit_pr) {
31
32
  needs_rebase = true;
32
33
  }
@@ -51,9 +52,9 @@ async function run(args) {
51
52
  }
52
53
  else {
53
54
  actions.output(React.createElement(Ink.Text, null, "\u2705 Everything up to date."));
54
- actions.output(React.createElement(Ink.Text, { color: "gray" },
55
+ actions.output(React.createElement(Ink.Text, { color: colors.gray },
55
56
  React.createElement(Ink.Text, null, "Run with"),
56
- React.createElement(Ink.Text, { bold: true, color: "yellow" }, ` --force `),
57
+ React.createElement(Ink.Text, { bold: true, color: colors.yellow }, ` --force `),
57
58
  React.createElement(Ink.Text, null, "to force update all pull requests.")));
58
59
  actions.exit(0);
59
60
  }
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { assertNever } from "../core/assertNever.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { invariant } from "../core/invariant.js";
5
6
  import { Store } from "./Store.js";
6
7
  import { Table } from "./Table.js";
@@ -11,14 +12,12 @@ export function StatusTable() {
11
12
  const row_list = [];
12
13
  for (const group of commit_range.group_list) {
13
14
  const row = {
14
- icon: "",
15
15
  count: "",
16
16
  status: "NEW",
17
17
  title: "",
18
18
  url: "",
19
19
  };
20
20
  if (group.id === commit_range.UNASSIGNED) {
21
- row.icon = "⭑";
22
21
  row.status = "NEW";
23
22
  row.title = "Unassigned";
24
23
  row.count = `0/${group.commits.length}`;
@@ -26,16 +25,13 @@ export function StatusTable() {
26
25
  }
27
26
  else {
28
27
  if (group.dirty) {
29
- row.icon = "!";
30
28
  row.status = "OUTDATED";
31
29
  }
32
30
  else {
33
- row.icon = "✔";
34
31
  row.status = "SYNCED";
35
32
  }
36
33
  if (group.pr) {
37
34
  if (group.pr.state === "MERGED") {
38
- row.icon = "↗";
39
35
  row.status = "MERGED";
40
36
  }
41
37
  row.title = group.pr.title;
@@ -49,25 +45,21 @@ export function StatusTable() {
49
45
  }
50
46
  row_list.push(row);
51
47
  }
52
- return (React.createElement(Table, { data: row_list, fillColumn: "title",
53
- // maxWidth={{
54
- // title: 30,
55
- // }}
56
- columnGap: 3, columns: {
57
- icon: IconColumn,
48
+ return (React.createElement(Table, { data: row_list, fillColumn: "title", maxWidth: {
49
+ status: (v) => v + 2,
50
+ }, columnGap: 3, columns: {
58
51
  status: StatusColumn,
59
52
  count: CountColumn,
60
53
  title: TitleColumn,
61
54
  url: UrlColumn,
62
55
  } }));
63
56
  }
64
- function IconColumn(props) {
65
- const value = props.row[props.column];
66
- return (React.createElement(Ink.Text, { color: get_status_color(props.row), bold: get_status_bold(props.row) }, value));
67
- }
68
57
  function StatusColumn(props) {
69
58
  const value = props.row[props.column];
70
- return (React.createElement(Ink.Text, { color: get_status_color(props.row), bold: get_status_bold(props.row) }, value));
59
+ return (React.createElement(Ink.Text, { color: get_status_color(props.row), bold: get_status_bold(props.row) },
60
+ get_status_icon(props.row),
61
+ " ",
62
+ value));
71
63
  }
72
64
  function CountColumn(props) {
73
65
  const value = props.row[props.column];
@@ -81,19 +73,35 @@ function UrlColumn(props) {
81
73
  const value = props.row[props.column];
82
74
  return React.createElement(Url, { dimColor: true }, value);
83
75
  }
76
+ function get_status_icon(row) {
77
+ switch (row.status) {
78
+ case "NEW":
79
+ return "⭑";
80
+ case "OUTDATED":
81
+ return "!";
82
+ case "MERGED":
83
+ return "↗";
84
+ case "SYNCED":
85
+ return "✔";
86
+ default:
87
+ assertNever(row.status);
88
+ return "?";
89
+ // unicode question mark in box
90
+ }
91
+ }
84
92
  function get_status_color(row) {
85
93
  switch (row.status) {
86
94
  case "NEW":
87
- return "yellow";
95
+ return colors.yellow;
88
96
  case "OUTDATED":
89
- return "red";
97
+ return colors.red;
90
98
  case "MERGED":
91
- return "purple";
99
+ return colors.purple;
92
100
  case "SYNCED":
93
- return "green";
101
+ return colors.green;
94
102
  default:
95
103
  assertNever(row.status);
96
- return "gray";
104
+ return colors.gray;
97
105
  }
98
106
  }
99
107
  function get_status_bold(row) {
package/dist/app/Store.js CHANGED
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { createStore, useStore } from "zustand";
4
4
  import { immer } from "zustand/middleware/immer";
5
+ import { colors } from "../core/colors.js";
5
6
  import { Exit } from "./Exit.js";
6
7
  const BaseStore = createStore()(immer((set, get) => ({
7
8
  argv: null,
@@ -9,6 +10,7 @@ const BaseStore = createStore()(immer((set, get) => ({
9
10
  cwd: null,
10
11
  username: null,
11
12
  repo_path: null,
13
+ master_branch: "master",
12
14
  head: null,
13
15
  merge_base: null,
14
16
  branch_name: null,
@@ -41,7 +43,7 @@ const BaseStore = createStore()(immer((set, get) => ({
41
43
  },
42
44
  error(message) {
43
45
  set((state) => {
44
- state.mutate.output(state, React.createElement(Ink.Text, { color: "#ef4444" }, message));
46
+ state.mutate.output(state, React.createElement(Ink.Text, { color: colors.red }, message));
45
47
  });
46
48
  },
47
49
  output(node) {
package/dist/app/Table.js CHANGED
@@ -6,8 +6,7 @@ export function Table(props) {
6
6
  return (React.createElement(Container, null,
7
7
  React.createElement(Ink.Text, { dimColor: true }, "No data found.")));
8
8
  }
9
- const sample_row = props.data[0];
10
- const RowColumnList = Object.keys(sample_row);
9
+ const RowColumnList = Object.keys(props.columns);
11
10
  // walk data and discover max width for each column
12
11
  const max_col_width = {};
13
12
  for (const col of RowColumnList) {
@@ -17,10 +16,12 @@ export function Table(props) {
17
16
  for (const col of RowColumnList) {
18
17
  const row_col = row[col];
19
18
  max_col_width[col] = Math.max(String(row_col).length, max_col_width[col]);
20
- const maxWidth = props.maxWidth?.[col];
21
- if (is_finite_value(maxWidth)) {
22
- max_col_width[col] = Math.min(maxWidth - 1, max_col_width[col]);
23
- }
19
+ }
20
+ }
21
+ for (const col of RowColumnList) {
22
+ const maxWidth = props.maxWidth?.[col];
23
+ if (maxWidth) {
24
+ max_col_width[col] = maxWidth(max_col_width[col]);
24
25
  }
25
26
  }
26
27
  const { stdout } = Ink.useStdout();
@@ -45,7 +46,7 @@ export function Table(props) {
45
46
  max_col_width[props.fillColumn] = Math.min(max_col_width[props.fillColumn], remaining_space);
46
47
  }
47
48
  }
48
- // console.debug({ available_width, remaining_space, max_col_width });
49
+ // console.debug({ available_width, max_col_width });
49
50
  return (React.createElement(Container, null, props.data.map((row, i) => {
50
51
  return (React.createElement(Ink.Box, { key: i,
51
52
  // borderStyle="round"
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  export function TextInput(props) {
4
5
  const [value, set_value] = React.useState(get_value(props));
5
6
  React.useEffect(function sync_value_prop() {
@@ -41,9 +42,9 @@ export function TextInput(props) {
41
42
  props.onChange?.(next_value);
42
43
  });
43
44
  // console.debug("[TextInput]", { value });
44
- return (React.createElement(Ink.Box, { borderStyle: "single", minHeight: 1, borderColor: "yellow", borderDimColor: true },
45
- React.createElement(Ink.Text, null, value || ""),
46
- !caret_visible ? null : (React.createElement(Ink.Text, { color: "yellow", dimColor: true }, "|"))));
45
+ return (React.createElement(Ink.Box, { borderStyle: "single", minHeight: 1, borderColor: colors.yellow, borderDimColor: true },
46
+ React.createElement(Ink.Text, null, value || ""),
47
+ React.createElement(Ink.Text, { color: colors.yellow, dimColor: true, inverse: caret_visible }, " ")));
47
48
  }
48
49
  function get_value(props) {
49
50
  return props.value || "";
package/dist/app/Url.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  export function Url(props) {
4
- const text_color = "#38bdf8";
5
- return (React.createElement(Ink.Text, { bold: true, color: text_color, ...props }, props.children));
5
+ return (React.createElement(Ink.Text, { bold: true, color: colors.blue, ...props }, props.children));
6
6
  }
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { colors } from "../core/colors.js";
3
4
  import { Parens } from "./Parens.js";
4
5
  export function YesNoPrompt(props) {
5
6
  const [answer, set_answer] = React.useState("");
@@ -14,8 +15,8 @@ export function YesNoPrompt(props) {
14
15
  }
15
16
  });
16
17
  // prettier-ignore
17
- const y = React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "Y");
18
- const n = React.createElement(Ink.Text, { color: "#ef4444" }, "n");
18
+ const y = React.createElement(Ink.Text, { bold: true, color: colors.green }, "Y");
19
+ const n = React.createElement(Ink.Text, { color: colors.red }, "n");
19
20
  let choices;
20
21
  switch (answer) {
21
22
  case "y":
@@ -32,8 +33,8 @@ export function YesNoPrompt(props) {
32
33
  }
33
34
  return (React.createElement(Ink.Box, { flexDirection: "column" },
34
35
  React.createElement(Ink.Box, null,
35
- typeof props.message === "object" ? (props.message) : (React.createElement(Ink.Text, { color: "yellow" }, props.message)),
36
+ typeof props.message === "object" ? (props.message) : (React.createElement(Ink.Text, { color: colors.yellow }, props.message)),
36
37
  React.createElement(Ink.Text, null, " "),
37
38
  React.createElement(Parens, null,
38
- React.createElement(Ink.Text, { color: "gray" }, choices)))));
39
+ React.createElement(Ink.Text, { color: colors.gray }, choices)))));
39
40
  }
package/dist/command.js CHANGED
@@ -26,6 +26,11 @@ export async function command() {
26
26
  alias: ["v"],
27
27
  default: false,
28
28
  description: "Enable verbose mode with more detailed output for debugging",
29
+ })
30
+ .option("branch", {
31
+ type: "string",
32
+ alias: ["b"],
33
+ description: `Set the master branch name, defaults to "master" (or "main" if "master" is not found)`,
29
34
  })
30
35
  .option("write-state-json", {
31
36
  hidden: true,
@@ -1,12 +1,14 @@
1
+ import { Store } from "../app/Store.js";
1
2
  import * as Metadata from "./Metadata.js";
2
3
  import { cli } from "./cli.js";
3
4
  import * as github from "./github.js";
4
5
  export async function range(commit_group_map) {
6
+ const master_branch = Store.getState().master_branch;
5
7
  // gather all open prs in repo first
6
8
  // cheaper query to populate cache
7
9
  await github.pr_list();
8
10
  const commit_list = await get_commit_list();
9
- const pr_map = new Map();
11
+ const pr_lookup = {};
10
12
  let invalid = false;
11
13
  const group_map = new Map();
12
14
  for (const commit of commit_list) {
@@ -63,7 +65,7 @@ export async function range(commit_group_map) {
63
65
  const pr_result = await github.pr_status(group.id);
64
66
  if (pr_result && pr_result.state !== "CLOSED") {
65
67
  group.pr = pr_result;
66
- pr_map.set(group.id, pr_result);
68
+ pr_lookup[group.id] = pr_result;
67
69
  }
68
70
  }
69
71
  // console.debug("group", group.pr?.title.substring(0, 40));
@@ -75,7 +77,7 @@ export async function range(commit_group_map) {
75
77
  group_list.push(group);
76
78
  }
77
79
  if (i === 0) {
78
- group.base = "master";
80
+ group.base = master_branch;
79
81
  }
80
82
  else {
81
83
  const last_group = group_value_list[i - 1];
@@ -119,7 +121,7 @@ export async function range(commit_group_map) {
119
121
  if (unassigned_group) {
120
122
  group_list.unshift(unassigned_group);
121
123
  }
122
- return { invalid, group_list, commit_list, pr_map, UNASSIGNED };
124
+ return { invalid, group_list, commit_list, pr_lookup, UNASSIGNED };
123
125
  }
124
126
  async function get_commit_list() {
125
127
  const log_result = await cli(`git log master..HEAD --oneline --format=%H --color=never`);
@@ -1,17 +1,19 @@
1
1
  export function write(args) {
2
- const group_list = args.commit_range?.group_list;
3
- if (!Array.isArray(group_list) || group_list.length === 0) {
4
- return "";
5
- }
6
2
  const stack_list = [];
7
- for (const group of group_list) {
8
- if (group.pr?.url) {
9
- const selected = args.selected_group_id === group.id;
3
+ for (const pr_url of args.pr_url_list) {
4
+ if (pr_url) {
5
+ const selected = args.selected_url === pr_url;
10
6
  const icon = selected ? "👉" : "⏳";
11
- stack_list.push(`- ${icon} ${group.pr.url}`);
7
+ stack_list.push(`- ${icon} ${pr_url}`);
12
8
  }
13
9
  }
14
- const stack_table = TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
10
+ let stack_table;
11
+ if (stack_list.length > 1) {
12
+ stack_table = TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
13
+ }
14
+ else {
15
+ stack_table = "";
16
+ }
15
17
  let result = args.body;
16
18
  if (RE.stack_table.test(result)) {
17
19
  // replace stack table
@@ -0,0 +1,83 @@
1
+ import chalk from "chalk";
2
+ function create_color_proxy(base) {
3
+ return new Proxy(base, {
4
+ get(target, prop) {
5
+ switch (prop) {
6
+ case "test":
7
+ return test;
8
+ case "bracket":
9
+ return (str) => [
10
+ target.bold.whiteBright("["),
11
+ str,
12
+ target.bold.whiteBright("]"),
13
+ ].join("");
14
+ case "url":
15
+ return target.bold.underline.blueBright;
16
+ case "cmd":
17
+ return target.bold.yellow;
18
+ case "branch":
19
+ return target.bold.green;
20
+ }
21
+ const target_prop = target[prop];
22
+ return target_prop;
23
+ },
24
+ apply(target, _this_arg, arguments_list) {
25
+ return target(...arguments_list);
26
+ },
27
+ });
28
+ }
29
+ export const color = create_color_proxy(chalk);
30
+ function test() {
31
+ const PROP_LIST = [
32
+ "reset",
33
+ "bold",
34
+ "dim",
35
+ "italic",
36
+ "underline",
37
+ "overline",
38
+ "inverse",
39
+ "hidden",
40
+ "strikethrough",
41
+ "visible",
42
+ "black",
43
+ "red",
44
+ "green",
45
+ "yellow",
46
+ "blue",
47
+ "magenta",
48
+ "cyan",
49
+ "white",
50
+ "blackBright",
51
+ "gray",
52
+ "grey",
53
+ "redBright",
54
+ "greenBright",
55
+ "yellowBright",
56
+ "blueBright",
57
+ "magentaBright",
58
+ "cyanBright",
59
+ "whiteBright",
60
+ "bgBlack",
61
+ "bgRed",
62
+ "bgGreen",
63
+ "bgYellow",
64
+ "bgBlue",
65
+ "bgMagenta",
66
+ "bgCyan",
67
+ "bgWhite",
68
+ "bgBlackBright",
69
+ "bgGray",
70
+ "bgGrey",
71
+ "bgRedBright",
72
+ "bgGreenBright",
73
+ "bgYellowBright",
74
+ "bgBlueBright",
75
+ "bgMagentaBright",
76
+ "bgCyanBright",
77
+ "bgWhiteBright",
78
+ ];
79
+ for (const prop of PROP_LIST) {
80
+ // eslint-disable-next-line no-console
81
+ console.debug(chalk[prop](prop));
82
+ }
83
+ }
@@ -0,0 +1,15 @@
1
+ // ink uses chalk internally
2
+ // https://github.com/vadimdemedes/ink#color
3
+ export const colors = {
4
+ red: "rgb(248, 81, 73)",
5
+ // red-emphasis rgb(218, 54, 51)
6
+ green: "rgb(63, 185, 80)",
7
+ // green-emphasis rgb(35, 134, 54)
8
+ purple: "rgb(163, 113, 247)",
9
+ // purple-emphasis rgb(137, 87, 229)
10
+ blue: "rgb(47, 129, 247)",
11
+ orange: "rgb(255, 166, 87)",
12
+ yellow: "rgb(234, 179, 8)",
13
+ gray: "rgb(110, 118, 129)",
14
+ lightGray: "rgb(125, 133, 144)",
15
+ };
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import * as Ink from "ink";
6
6
  import { Brackets } from "../app/Brackets.js";
7
7
  import { Store } from "../app/Store.js";
8
+ import { colors } from "../core/colors.js";
8
9
  import { cli } from "./cli.js";
9
10
  import { invariant } from "./invariant.js";
10
11
  import { safe_quote } from "./safe_quote.js";
@@ -27,7 +28,7 @@ export async function pr_list() {
27
28
  if (actions.isDebug()) {
28
29
  actions.output(React.createElement(Ink.Text, { dimColor: true },
29
30
  React.createElement(Ink.Text, null, "Github cache "),
30
- React.createElement(Ink.Text, { bold: true, color: "yellow" }, result_pr_list.length),
31
+ React.createElement(Ink.Text, { bold: true, color: colors.yellow }, result_pr_list.length),
31
32
  React.createElement(Ink.Text, null, " open PRs from "),
32
33
  React.createElement(Brackets, null, repo_path),
33
34
  React.createElement(Ink.Text, null, " authored by "),
@@ -53,7 +54,7 @@ export async function pr_status(branch) {
53
54
  actions.output(React.createElement(Ink.Text, null,
54
55
  React.createElement(Ink.Text, { dimColor: true }, "Github pr_status cache"),
55
56
  React.createElement(Ink.Text, null, " "),
56
- React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "HIT "),
57
+ React.createElement(Ink.Text, { bold: true, color: colors.green }, "HIT "),
57
58
  React.createElement(Ink.Text, null, " "),
58
59
  React.createElement(Ink.Text, { dimColor: true }, branch)));
59
60
  }
@@ -63,7 +64,7 @@ export async function pr_status(branch) {
63
64
  actions.output(React.createElement(Ink.Text, null,
64
65
  React.createElement(Ink.Text, { dimColor: true }, "Github pr_status cache"),
65
66
  React.createElement(Ink.Text, null, " "),
66
- React.createElement(Ink.Text, { bold: true, color: "#ef4444" }, "MISS"),
67
+ React.createElement(Ink.Text, { bold: true, color: colors.red }, "MISS"),
67
68
  React.createElement(Ink.Text, null, " "),
68
69
  React.createElement(Ink.Text, { dimColor: true }, branch)));
69
70
  }
@@ -85,7 +86,9 @@ export async function pr_create(args) {
85
86
  const cli_result = await cli(`gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`);
86
87
  if (cli_result.code !== 0) {
87
88
  handle_error(cli_result.output);
89
+ return null;
88
90
  }
91
+ return cli_result.stdout;
89
92
  }
90
93
  export async function pr_edit(args) {
91
94
  const cli_result = await cli(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -21,7 +21,8 @@
21
21
  "dev": "tsc --watch",
22
22
  "lint": "eslint . --fix",
23
23
  "prettier": "prettier ./src --write",
24
- "test": "npm run prettier && npm run lint && npm run build"
24
+ "test": "npm run prettier && npm run lint && npm run build",
25
+ "prepublishOnly": "npm test"
25
26
  },
26
27
  "dependencies": {
27
28
  "chalk": "^5.3.0",