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 +1 -1
- package/dist/__fixtures__/metadata.js +7 -30
- package/dist/app/AutoUpdate.js +5 -4
- package/dist/app/Brackets.js +3 -2
- package/dist/app/Command.js +2 -1
- package/dist/app/Debug.js +2 -1
- package/dist/app/DependencyCheck.js +10 -9
- package/dist/app/GatherMetadata.js +37 -4
- package/dist/app/GithubApiError.js +3 -2
- package/dist/app/LocalCommitStatus.js +2 -1
- package/dist/app/LocalMergeRebase.js +12 -10
- package/dist/app/ManualRebase.js +47 -13
- package/dist/app/MultiSelect.js +7 -5
- package/dist/app/Parens.js +2 -1
- package/dist/app/SelectCommitRanges.js +15 -14
- package/dist/app/Status.js +4 -3
- package/dist/app/StatusTable.js +29 -21
- package/dist/app/Store.js +3 -1
- package/dist/app/Table.js +8 -7
- package/dist/app/TextInput.js +4 -3
- package/dist/app/Url.js +2 -2
- package/dist/app/YesNoPrompt.js +5 -4
- package/dist/command.js +5 -0
- package/dist/core/CommitMetadata.js +6 -4
- package/dist/core/StackSummaryTable.js +11 -9
- package/dist/core/chalk.js +83 -0
- package/dist/core/colors.js +15 -0
- package/dist/core/github.js +6 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,40 +5,16 @@ export const METADATA = {
|
|
|
5
5
|
"invalid": false,
|
|
6
6
|
"group_list": [
|
|
7
7
|
{
|
|
8
|
-
"id": "
|
|
9
|
-
"pr":
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
41
|
-
"branch_id":
|
|
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": {
|
package/dist/app/AutoUpdate.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
102
|
-
version: (React.createElement(Ink.Text, { color:
|
|
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`);
|
package/dist/app/Brackets.js
CHANGED
|
@@ -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 =
|
|
5
|
-
const text_color =
|
|
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,
|
package/dist/app/Command.js
CHANGED
|
@@ -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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 ===
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
28
|
-
const master_sha = (await cli(`git rev-parse
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
113
|
+
actions.exit(6);
|
|
112
114
|
}
|
|
113
115
|
}
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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;
|
package/dist/app/MultiSelect.js
CHANGED
|
@@ -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((
|
|
56
|
+
Ink.useInput((input, key) => {
|
|
56
57
|
if (props.disabled) {
|
|
57
58
|
// console.debug("[MultiSelect] disabled, ignoring input");
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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));
|
package/dist/app/Parens.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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:
|
|
159
|
-
count: (React.createElement(Ink.Text, { color:
|
|
160
|
-
c: (React.createElement(Ink.Text, { bold: true, color:
|
|
161
|
-
create: (React.createElement(Ink.Text, { bold: true, color:
|
|
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:
|
|
167
|
-
sync: (React.createElement(Ink.Text, { bold: true, color:
|
|
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:
|
|
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:
|
|
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:
|
|
183
|
-
left: (React.createElement(Ink.Text, { bold: true, color:
|
|
184
|
-
right: (React.createElement(Ink.Text, { bold: true, color:
|
|
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:
|
|
188
|
-
enter: (React.createElement(Ink.Text, { bold: true, color:
|
|
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();
|
package/dist/app/Status.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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:
|
|
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
|
}
|
package/dist/app/StatusTable.js
CHANGED
|
@@ -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
|
-
|
|
54
|
-
|
|
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) },
|
|
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
|
|
95
|
+
return colors.yellow;
|
|
88
96
|
case "OUTDATED":
|
|
89
|
-
return
|
|
97
|
+
return colors.red;
|
|
90
98
|
case "MERGED":
|
|
91
|
-
return
|
|
99
|
+
return colors.purple;
|
|
92
100
|
case "SYNCED":
|
|
93
|
-
return
|
|
101
|
+
return colors.green;
|
|
94
102
|
default:
|
|
95
103
|
assertNever(row.status);
|
|
96
|
-
return
|
|
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:
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,
|
|
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"
|
package/dist/app/TextInput.js
CHANGED
|
@@ -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:
|
|
45
|
-
React.createElement(Ink.Text, null, value || "
|
|
46
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/app/YesNoPrompt.js
CHANGED
|
@@ -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:
|
|
18
|
-
const n = React.createElement(Ink.Text, { color:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
8
|
-
if (
|
|
9
|
-
const selected = args.
|
|
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} ${
|
|
7
|
+
stack_list.push(`- ${icon} ${pr_url}`);
|
|
12
8
|
}
|
|
13
9
|
}
|
|
14
|
-
|
|
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
|
+
};
|
package/dist/core/github.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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.
|
|
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",
|