git-stack-cli 0.2.1 → 0.3.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.
package/README.md CHANGED
@@ -15,13 +15,15 @@ git multi-diff
15
15
  npm run dev
16
16
  npm link
17
17
 
18
- git stack
19
- git multi-diff
18
+ git stack --debug
19
+ git multi-diff --debug
20
20
  ```
21
21
 
22
22
 
23
23
  ## TODO
24
24
 
25
+ - point value for querying all pr for user vs individual pr status (current)?
26
+
25
27
  - select commit ranges
26
28
  - capture PR title when creating new group
27
29
 
@@ -1,5 +1,7 @@
1
1
  // prettier-ignore
2
2
  export const METADATA = {
3
+ "username": "magus",
4
+ "repo_path": "magus/git-multi-diff-playground",
3
5
  "head": "1a50c5fe3cd129547c5c34a54d1611ec06ab213e",
4
6
  "merge_base": "9528176b12abf81c779bc5244afc7d760f6fa422",
5
7
  "branch_name": "dev/noah/a-test",
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ import * as Ink from "ink";
3
+ export function Brackets(props) {
4
+ const color = "#f97316";
5
+ const text_color = "#06b6d4";
6
+ return (React.createElement(Ink.Text, { color: text_color },
7
+ React.createElement(Ink.Text, { color: color }, "["),
8
+ props.children,
9
+ React.createElement(Ink.Text, { color: color }, "]")));
10
+ }
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  export function Brackets(props) {
4
4
  const color = "#f97316";
5
- const text_color = "#06b6d4";
5
+ const text_color = "#38bdf8";
6
6
  return (React.createElement(Ink.Text, { color: text_color },
7
7
  React.createElement(Ink.Text, { color: color }, "["),
8
8
  props.children,
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import * as Ink from "ink";
3
+ export function Command(props) {
4
+ const text_color = "#f97316";
5
+ return (React.createElement(Ink.Text, { bold: true, color: text_color }, props.children));
6
+ }
package/dist/app/Debug.js CHANGED
@@ -8,15 +8,16 @@ import { Store } from "./Store.js";
8
8
  export function Debug() {
9
9
  const actions = Store.useActions();
10
10
  const state = Store.useState((state) => state);
11
- const debug = Store.useState((state) => state.argv?.debug);
11
+ const argv = Store.useState((state) => state.argv);
12
12
  React.useEffect(function debugMessageOnce() {
13
- if (debug) {
14
- actions.output(React.createElement(Ink.Text, { color: "yellow" }, "Debug mode enabled"));
13
+ actions.debug(React.createElement(Ink.Text, { color: "yellow" }, "Debug mode enabled"));
14
+ if (argv?.verbose) {
15
+ actions.debug(React.createElement(Ink.Text, { dimColor: true }, JSON.stringify(argv, null, 2)));
15
16
  }
16
- }, [debug]);
17
+ }, [argv]);
17
18
  React.useEffect(function syncStateJson() {
18
19
  invariant(state.cwd, "state.cwd must exist");
19
- if (!debug) {
20
+ if (!argv?.["write-state-json"]) {
20
21
  return;
21
22
  }
22
23
  const output_file = path.join(state.cwd, "git-multi-diff-state.json");
@@ -26,6 +27,6 @@ export function Debug() {
26
27
  const serialized = json.serialize(state);
27
28
  const content = JSON.stringify(serialized, null, 2);
28
29
  fs.writeFileSync(output_file, content);
29
- }, [debug, state]);
30
+ }, [argv, state]);
30
31
  return null;
31
32
  }
@@ -2,51 +2,68 @@ import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { cli } from "../core/cli.js";
4
4
  import { is_command_available } from "../core/is_command_available.js";
5
+ import { match_group } from "../core/match_group.js";
5
6
  import { Await } from "./Await.js";
7
+ import { Command } from "./Command.js";
8
+ import { Parens } from "./Parens.js";
6
9
  import { Store } from "./Store.js";
10
+ import { Url } from "./Url.js";
7
11
  export function DependencyCheck(props) {
8
12
  const actions = Store.useActions();
9
- return (React.createElement(Await, { fallback: React.createElement(Ink.Box, null,
10
- React.createElement(Ink.Text, null, "Checking git install...")), function: async () => {
13
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
14
+ "Checking ",
15
+ React.createElement(Command, null, "git"),
16
+ " install..."), function: async () => {
11
17
  if (is_command_available("git")) {
12
18
  return;
13
19
  }
14
- actions.output(React.createElement(Ink.Text, null,
15
- React.createElement(Ink.Text, { color: "yellow" }, "git"),
20
+ actions.output(React.createElement(Ink.Text, { color: "yellow" },
21
+ React.createElement(Command, null, "git"),
16
22
  " must be installed."));
17
23
  actions.exit(2);
18
24
  } },
19
- React.createElement(Await, { fallback: React.createElement(Ink.Box, null,
20
- React.createElement(Ink.Text, null, "Checking gh install...")), function: async () => {
25
+ React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
26
+ React.createElement(Ink.Text, null,
27
+ "Checking ",
28
+ React.createElement(Command, null, "gh"),
29
+ " install...")), function: async () => {
21
30
  if (is_command_available("gh")) {
22
31
  return;
23
32
  }
24
- actions.output(React.createElement(Ink.Text, null,
25
- React.createElement(Ink.Text, { color: "yellow" }, "gh"),
33
+ actions.output(React.createElement(Ink.Text, { color: "yellow" },
34
+ React.createElement(Command, null, "gh"),
26
35
  " must be installed."));
27
- actions.output(React.createElement(Ink.Box, { flexDirection: "row", gap: 1 },
28
- React.createElement(Ink.Text, null, "Visit"),
29
- React.createElement(Ink.Text, { color: "#38bdf8" }, "https://cli.github.com"),
30
- React.createElement(Ink.Text, null, "to install the github cli"),
31
- React.createElement(Ink.Text, null,
32
- "(",
33
- React.createElement(Ink.Text, { color: "yellow" }, "gh"),
34
- ")")));
36
+ actions.output(React.createElement(Ink.Text, { color: "yellow" },
37
+ React.createElement(Ink.Text, null, "Visit "),
38
+ React.createElement(Url, null, "https://cli.github.com"),
39
+ React.createElement(Ink.Text, null, " to install the github cli "),
40
+ React.createElement(Parens, null,
41
+ React.createElement(Command, null, "gh"))));
35
42
  actions.exit(3);
36
43
  } },
37
- React.createElement(Await, { fallback: React.createElement(Ink.Box, null,
38
- React.createElement(Ink.Text, null, "Checking gh auth status...")), function: async () => {
39
- const gh_auth_status_cli = await cli(`gh auth status`, {
44
+ React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
45
+ React.createElement(Ink.Text, null,
46
+ "Checking ",
47
+ React.createElement(Command, null, "gh auth status"),
48
+ "...")), function: async () => {
49
+ const auth_output = await cli(`gh auth status`, {
40
50
  ignoreExitCode: true,
41
51
  });
42
- if (gh_auth_status_cli.code === 0) {
52
+ if (auth_output.code === 0) {
53
+ const username = match_group(auth_output.stdout, RE.auth_username, "username");
54
+ actions.set((state) => {
55
+ state.username = username;
56
+ });
43
57
  return;
44
58
  }
45
- actions.output(React.createElement(Ink.Box, { flexDirection: "row", gap: 1 },
46
- React.createElement(Ink.Text, { color: "yellow" }, "gh"),
47
- React.createElement(Ink.Text, null, "requires login, please run"),
48
- React.createElement(Ink.Text, null,
49
- React.createElement(Ink.Text, { color: "yellow" }, "gh auth login"))));
59
+ actions.output(React.createElement(Ink.Text, { color: "yellow" },
60
+ React.createElement(Command, null, "gh"),
61
+ React.createElement(Ink.Text, null, " requires login, please run "),
62
+ React.createElement(Command, null, "gh auth login")));
50
63
  actions.exit(4);
51
64
  } }, props.children))));
52
65
  }
66
+ const RE = {
67
+ // Logged in to github.com as magus
68
+ auth_username: /Logged in to github.com as (?<username>[^\s]+)/,
69
+ };
@@ -4,6 +4,7 @@ import * as CommitMetadata from "../core/CommitMetadata.js";
4
4
  import { cli } from "../core/cli.js";
5
5
  import { invariant } from "../core/invariant.js";
6
6
  import * as json from "../core/json.js";
7
+ import { match_group } from "../core/match_group.js";
7
8
  import { Await } from "./Await.js";
8
9
  import { Store } from "./Store.js";
9
10
  export function GatherMetadata(props) {
@@ -34,13 +35,20 @@ async function gather_metadata() {
34
35
  actions.exit(0);
35
36
  return;
36
37
  }
38
+ // git@github.com:magus/git-multi-diff-playground.git
39
+ // https://github.com/magus/git-multi-diff-playground.git
40
+ const origin_url = (await cli(`git config --get remote.origin.url`)).stdout;
41
+ const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
37
42
  const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
43
+ Store.setState((state) => {
44
+ state.repo_path = repo_path;
45
+ state.head = head;
46
+ state.merge_base = merge_base;
47
+ state.branch_name = branch_name;
48
+ });
38
49
  try {
39
50
  const commit_range = await CommitMetadata.range();
40
51
  Store.setState((state) => {
41
- state.head = head;
42
- state.merge_base = merge_base;
43
- state.branch_name = branch_name;
44
52
  state.commit_range = commit_range;
45
53
  state.step = "status";
46
54
  });
@@ -52,3 +60,8 @@ async function gather_metadata() {
52
60
  }
53
61
  }
54
62
  }
63
+ const RE = {
64
+ // git@github.com:magus/git-multi-diff-playground.git
65
+ // https://github.com/magus/git-multi-diff-playground.git
66
+ repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
67
+ };
@@ -9,10 +9,10 @@ import { invariant } from "../core/invariant.js";
9
9
  import { Await } from "./Await.js";
10
10
  import { Brackets } from "./Brackets.js";
11
11
  import { Store } from "./Store.js";
12
- export function ManualRebase() {
13
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: run }));
12
+ export function ManualRebase(props) {
13
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
14
14
  }
15
- async function run() {
15
+ async function run(props) {
16
16
  const state = Store.getState();
17
17
  const actions = state.actions;
18
18
  const branch_name = state.branch_name;
@@ -61,21 +61,23 @@ async function run() {
61
61
  "Syncing ",
62
62
  React.createElement(Brackets, null, group.pr?.title || group.id),
63
63
  "..."));
64
- // push to origin since github requires commit shas to line up perfectly
65
- await cli(`git push -f origin HEAD:${group.id}`);
66
- if (group.pr) {
67
- // ensure base matches pr in github
68
- await github.pr_base(group.id, group.base);
69
- }
70
- else {
71
- // delete local group branch if leftover
72
- await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
73
- // move to temporary branch for creating pr
74
- await cli(`git checkout -b ${group.id}`);
75
- // create pr in github
76
- await github.pr_create(group.id, group.base);
77
- // move back to temp branch
78
- await cli(`git checkout ${temp_branch_name}`);
64
+ if (!props.skipSync) {
65
+ // push to origin since github requires commit shas to line up perfectly
66
+ await cli(`git push -f origin HEAD:${group.id}`);
67
+ if (group.pr) {
68
+ // ensure base matches pr in github
69
+ await github.pr_base(group.id, group.base);
70
+ }
71
+ else {
72
+ // delete local group branch if leftover
73
+ await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
74
+ // move to temporary branch for creating pr
75
+ await cli(`git checkout -b ${group.id}`);
76
+ // create pr in github
77
+ await github.pr_create(group.id, group.base);
78
+ // move back to temp branch
79
+ await cli(`git checkout ${temp_branch_name}`);
80
+ }
79
81
  }
80
82
  }
81
83
  // after all commits have been cherry-picked and amended
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  export function Parens(props) {
4
- const color = "#06b6d4";
4
+ const color = "#38bdf8";
5
5
  return (React.createElement(Ink.Text, null,
6
6
  React.createElement(Ink.Text, { color: color }, "("),
7
7
  props.children,
@@ -59,7 +59,7 @@ function SelectCommitRangesInternal(props) {
59
59
  const current_index = group_list.findIndex((g) => g.id === selected_group_id);
60
60
  Ink.useInput((input, key) => {
61
61
  const inputLower = input.toLowerCase();
62
- if (unassigned_count === 0 && inputLower === "s") {
62
+ if (unassigned_count === 0 && (inputLower === "r" || inputLower === "s")) {
63
63
  actions.set((state) => {
64
64
  state.commit_map = {};
65
65
  for (const [sha, id] of commit_map.entries()) {
@@ -67,7 +67,13 @@ function SelectCommitRangesInternal(props) {
67
67
  state.commit_map[sha] = id;
68
68
  }
69
69
  }
70
- state.step = "manual-rebase";
70
+ switch (inputLower) {
71
+ case "s":
72
+ state.step = "manual-rebase";
73
+ break;
74
+ case "r":
75
+ state.step = "manual-rebase-no-sync";
76
+ }
71
77
  });
72
78
  return;
73
79
  }
@@ -154,14 +160,23 @@ function SelectCommitRangesInternal(props) {
154
160
  React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
155
161
  React.createElement(Parens, null, "c"),
156
162
  "reate"),
157
- " a new group")))) : (React.createElement(Ink.Text, null,
158
- "🎉 Done! Press ",
159
- React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "s"),
160
- " to ",
161
- React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
162
- React.createElement(Parens, null, "s"),
163
- "ync"),
164
- " the commits to Github")),
163
+ " a new group")))) : (React.createElement(React.Fragment, null,
164
+ React.createElement(Ink.Text, null,
165
+ "🎉 Done! Press ",
166
+ React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "s"),
167
+ " to ",
168
+ React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
169
+ React.createElement(Parens, null, "s"),
170
+ "ync"),
171
+ " the commits to Github"),
172
+ React.createElement(Ink.Text, { color: "gray" },
173
+ React.createElement(Ink.Text, null, "Press "),
174
+ React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "r"),
175
+ " to locally ",
176
+ React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
177
+ React.createElement(Parens, null, "r"),
178
+ "ebase"),
179
+ " only"))),
165
180
  React.createElement(Ink.Box, null,
166
181
  React.createElement(Ink.Text, { color: "gray" },
167
182
  React.createElement(Ink.Text, null, "Press "),
@@ -15,7 +15,7 @@ export function StatusTable() {
15
15
  title: "",
16
16
  url: "",
17
17
  };
18
- if (!group.pr) {
18
+ if (group.id === commit_range.UNASSIGNED) {
19
19
  row.icon = "⭑";
20
20
  row.status = "NEW";
21
21
  row.title = "Unassigned";
@@ -31,9 +31,15 @@ export function StatusTable() {
31
31
  row.icon = "✔";
32
32
  row.status = "SYNCED";
33
33
  }
34
- row.title = group.pr.title;
35
- row.count = `${group.pr.commits.length}/${group.commits.length}`;
36
- row.url = group.pr.url;
34
+ if (group.pr) {
35
+ row.title = group.pr.title;
36
+ row.count = `${group.pr.commits.length}/${group.commits.length}`;
37
+ row.url = group.pr.url;
38
+ }
39
+ else {
40
+ row.title = group.id;
41
+ row.count = `0/${group.commits.length}`;
42
+ }
37
43
  }
38
44
  row_list.push(row);
39
45
  }
package/dist/app/Store.js CHANGED
@@ -7,6 +7,8 @@ const BaseStore = createStore()(immer((set, get) => ({
7
7
  argv: null,
8
8
  ink: null,
9
9
  cwd: null,
10
+ username: null,
11
+ repo_path: null,
10
12
  head: null,
11
13
  merge_base: null,
12
14
  branch_name: null,
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import * as Ink from "ink";
3
+ export function Url(props) {
4
+ const text_color = "#38bdf8";
5
+ return React.createElement(Ink.Text, { color: text_color }, props.children);
6
+ }
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import * as Ink from "ink";
3
+ export function Url(props) {
4
+ const text_color = "#38bdf8";
5
+ return (React.createElement(Ink.Text, { bold: true, color: text_color }, props.children));
6
+ }
@@ -2,8 +2,10 @@ import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { Parens } from "./Parens.js";
4
4
  export function YesNoPrompt(props) {
5
+ const [answer, set_answer] = React.useState("");
5
6
  Ink.useInput((input) => {
6
7
  const inputLower = input.toLowerCase();
8
+ set_answer(inputLower);
7
9
  switch (inputLower) {
8
10
  case "n":
9
11
  return props.onNo();
@@ -17,7 +19,7 @@ export function YesNoPrompt(props) {
17
19
  React.createElement(Ink.Text, null, " "),
18
20
  React.createElement(Parens, null,
19
21
  React.createElement(Ink.Text, { color: "gray" },
20
- React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "Y"),
21
- "/",
22
- React.createElement(Ink.Text, { color: "#ef4444" }, "n"))))));
22
+ answer && answer !== "y" ? null : (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "Y")),
23
+ answer ? null : React.createElement(Ink.Text, null, "/"),
24
+ answer && answer !== "n" ? null : (React.createElement(Ink.Text, { color: "#ef4444" }, "n")))))));
23
25
  }
package/dist/app/main.js CHANGED
@@ -22,6 +22,8 @@ export function Main() {
22
22
  return React.createElement(SelectCommitRanges, null);
23
23
  case "manual-rebase":
24
24
  return React.createElement(ManualRebase, null);
25
+ case "manual-rebase-no-sync":
26
+ return React.createElement(ManualRebase, { skipSync: true });
25
27
  case "post-rebase-status":
26
28
  return React.createElement(PostRebaseStatus, null);
27
29
  default:
package/dist/command.js CHANGED
@@ -1,22 +1,62 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
3
  export async function command() {
4
- return yargs(hideBin(process.argv))
4
+ const debug_argv = await yargs(hideBin(process.argv))
5
+ .option("debug", {
6
+ type: "boolean",
7
+ description: "Enable debug mode with more options for debugging",
8
+ })
9
+ .help(false).argv;
10
+ if (!debug_argv.debug) {
11
+ return NormalMode();
12
+ }
13
+ return DebugMode();
14
+ }
15
+ function NormalMode() {
16
+ return (yargs(hideBin(process.argv))
17
+ .option("force", {
18
+ type: "boolean",
19
+ description: "Force sync even if no changes are detected",
20
+ })
21
+ .option("check", {
22
+ type: "boolean",
23
+ description: "Print status table without syncing",
24
+ })
25
+ .option("debug", {
26
+ type: "boolean",
27
+ description: "Enable debug mode with more options for debugging",
28
+ })
29
+ // disallow unknown options
30
+ .strict()
31
+ .help().argv);
32
+ }
33
+ function DebugMode() {
34
+ return (yargs(hideBin(process.argv))
5
35
  .option("force", {
6
36
  type: "boolean",
7
- description: "force",
37
+ description: "Force sync even if no changes are detected",
8
38
  })
9
39
  .option("check", {
10
40
  type: "boolean",
11
- description: "check",
41
+ description: "Print status table without syncing",
12
42
  })
13
43
  .option("debug", {
14
44
  type: "boolean",
15
- description: "debug",
45
+ description: "Enable debug mode with more options for debugging",
46
+ })
47
+ .option("verbose", {
48
+ type: "boolean",
49
+ description: "Log extra information during execution",
50
+ })
51
+ .option("write-state-json", {
52
+ type: "boolean",
53
+ description: "Write state to local json file for debugging",
16
54
  })
17
55
  .option("mock-metadata", {
18
56
  type: "boolean",
19
- description: "mock-metadata",
57
+ description: "Mock local store metadata for testing",
20
58
  })
21
- .help().argv;
59
+ // disallow unknown options
60
+ .strict()
61
+ .help().argv);
22
62
  }
@@ -2,6 +2,9 @@ import * as Metadata from "./Metadata.js";
2
2
  import { cli } from "./cli.js";
3
3
  import * as github from "./github.js";
4
4
  export async function range(commit_map) {
5
+ // gather all open prs in repo first
6
+ // cheaper query to populate cache
7
+ await github.pr_list();
5
8
  const commit_list = await get_commit_list();
6
9
  let invalid = false;
7
10
  const group_map = new Map();
@@ -11,9 +14,8 @@ export async function range(commit_map) {
11
14
  if (commit_map) {
12
15
  id = commit_map[commit.sha];
13
16
  }
14
- const pr = commit.pr;
15
- if (!pr) {
16
- // console.debug("INVALID", "MISSING PR", commit.message);
17
+ if (!id) {
18
+ // console.debug("INVALID", "MISSING ID", commit.message);
17
19
  invalid = true;
18
20
  }
19
21
  if (id) {
@@ -33,7 +35,7 @@ export async function range(commit_map) {
33
35
  }
34
36
  const group = group_map.get(id) || {
35
37
  id,
36
- pr,
38
+ pr: null,
37
39
  base: null,
38
40
  dirty: false,
39
41
  commits: [],
@@ -47,6 +49,12 @@ export async function range(commit_map) {
47
49
  let unassigned_group;
48
50
  for (let i = 0; i < group_value_list.length; i++) {
49
51
  const group = group_value_list[i];
52
+ if (group.id !== UNASSIGNED) {
53
+ const pr_result = await github.pr_status(group.id);
54
+ if (pr_result && pr_result.state === "OPEN") {
55
+ group.pr = pr_result;
56
+ }
57
+ }
50
58
  // console.debug("group", group.pr?.title.substring(0, 40));
51
59
  // console.debug(" ", "id", group.id);
52
60
  if (group.id === UNASSIGNED) {
@@ -117,18 +125,10 @@ export async function commit(sha) {
117
125
  const raw_message = (await cli(`git show -s --format=%B ${sha}`)).stdout;
118
126
  const branch_id = await Metadata.read(raw_message);
119
127
  const message = display_message(raw_message);
120
- let pr = null;
121
- if (branch_id) {
122
- const pr_result = await github.pr_status(branch_id);
123
- if (pr_result && pr_result.state === "OPEN") {
124
- pr = pr_result;
125
- }
126
- }
127
128
  return {
128
129
  sha,
129
130
  message,
130
131
  raw_message,
131
- pr,
132
132
  branch_id,
133
133
  };
134
134
  }
package/dist/core/cli.js CHANGED
@@ -20,6 +20,7 @@ export async function cli(command, unsafe_options) {
20
20
  }
21
21
  else {
22
22
  const result = {
23
+ command,
23
24
  code: code || 0,
24
25
  stdout: stdout.trimEnd(),
25
26
  stderr: stderr.trimEnd(),
@@ -40,5 +41,5 @@ cli.sync = function cli_sync(command, unsafe_options) {
40
41
  const stderr = String(spawn_return.stderr);
41
42
  const output = String(spawn_return.output);
42
43
  const code = spawn_return.status || 0;
43
- return { code, stdout, stderr, output };
44
+ return { command, code, stdout, stderr, output };
44
45
  };
@@ -0,0 +1,4 @@
1
+ export function env() {
2
+ const DEV = process.env["DEV"];
3
+ return { DEV };
4
+ }
@@ -1,20 +1,46 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
+ import { Brackets } from "../app/Brackets.js";
3
4
  import { Store } from "../app/Store.js";
4
5
  import { cli } from "./cli.js";
5
- export async function pr_status(branch) {
6
+ import { invariant } from "./invariant.js";
7
+ // prettier-ignore
8
+ const JSON_FIELDS = "--json number,state,baseRefName,headRefName,commits,title,url";
9
+ export async function pr_list() {
6
10
  const state = Store.getState();
7
11
  const actions = state.actions;
8
- const result = await cli(`gh pr view ${branch} --json number,state,baseRefName,headRefName,commits,title,url`, {
12
+ const username = state.username;
13
+ const repo_path = state.repo_path;
14
+ invariant(username, "username must exist");
15
+ invariant(repo_path, "repo_path must exist");
16
+ const cli_result = await cli(`gh pr list --repo ${repo_path} --author ${username} --state open ${JSON_FIELDS}`, {
9
17
  ignoreExitCode: true,
10
18
  });
11
- if (result.code !== 0) {
12
- actions.output(React.createElement(Ink.Text, { color: "#ef4444" }, result.output));
13
- actions.set((state) => {
14
- state.step = "github-api-error";
15
- });
16
- throw new Error("Unable to fetch PR status");
19
+ if (cli_result.code !== 0) {
20
+ handle_error(cli_result.output);
17
21
  }
22
+ const result_pr_list = JSON.parse(cli_result.stdout);
23
+ actions.debug(React.createElement(Ink.Text, { dimColor: true },
24
+ React.createElement(Ink.Text, null, "Github cache "),
25
+ React.createElement(Ink.Text, { bold: true, color: "yellow" }, result_pr_list.length),
26
+ React.createElement(Ink.Text, null, " open PRs from "),
27
+ React.createElement(Brackets, null, repo_path),
28
+ React.createElement(Ink.Text, null, " authored by "),
29
+ React.createElement(Brackets, null, username)));
30
+ actions.set((state) => {
31
+ for (const pr of result_pr_list) {
32
+ state.pr[pr.headRefName] = pr;
33
+ }
34
+ });
35
+ return result_pr_list;
36
+ }
37
+ export async function pr_status(branch) {
38
+ const state = Store.getState();
39
+ const actions = state.actions;
40
+ const username = state.username;
41
+ const repo_path = state.repo_path;
42
+ invariant(username, "username must exist");
43
+ invariant(repo_path, "repo_path must exist");
18
44
  const cache = state.pr[branch];
19
45
  if (cache) {
20
46
  actions.debug(React.createElement(Ink.Text, null,
@@ -31,15 +57,36 @@ export async function pr_status(branch) {
31
57
  React.createElement(Ink.Text, { bold: true, color: "#ef4444" }, "MISS"),
32
58
  React.createElement(Ink.Text, null, " "),
33
59
  React.createElement(Ink.Text, { dimColor: true }, branch)));
34
- const pr = JSON.parse(result.stdout);
60
+ const cli_result = await cli(`gh pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`, {
61
+ ignoreExitCode: true,
62
+ });
63
+ if (cli_result.code !== 0) {
64
+ // handle_error(cli_result.output);
65
+ return null;
66
+ }
67
+ const pr = JSON.parse(cli_result.stdout);
35
68
  actions.set((state) => {
36
69
  state.pr[pr.headRefName] = pr;
37
70
  });
38
71
  return pr;
39
72
  }
40
73
  export async function pr_create(branch, base) {
41
- await cli(`gh pr create --fill --head ${branch} --base ${base}`);
74
+ const cli_result = await cli(`gh pr create --fill --head ${branch} --base ${base}`);
75
+ if (cli_result.code !== 0) {
76
+ handle_error(cli_result.output);
77
+ }
42
78
  }
43
79
  export async function pr_base(branch, base) {
44
- await cli(`gh pr edit ${branch} --base ${base}`);
80
+ const cli_result = await cli(`gh pr edit ${branch} --base ${base}`);
81
+ if (cli_result.code !== 0) {
82
+ handle_error(cli_result.output);
83
+ }
84
+ }
85
+ function handle_error(output) {
86
+ const state = Store.getState();
87
+ const actions = state.actions;
88
+ actions.set((state) => {
89
+ state.step = "github-api-error";
90
+ });
91
+ throw new Error(output);
45
92
  }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",