git-stack-cli 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/App.js +12 -6
- package/dist/app/AutoUpdate.js +15 -10
- package/dist/app/DependencyCheck.js +4 -0
- package/dist/app/GatherMetadata copy.js +37 -4
- package/dist/app/LocalMergeRebase.js +20 -3
- package/dist/app/ManualRebase.js +14 -7
- package/dist/app/Output.js +13 -3
- package/dist/app/RebaseCheck.js +56 -0
- package/dist/app/Store.js +43 -11
- package/dist/command.js +6 -0
- package/dist/core/StackSummaryTable.js +7 -2
- package/dist/core/cli.js +51 -21
- package/package.json +1 -1
package/dist/app/App.js
CHANGED
|
@@ -8,6 +8,7 @@ import { LocalCommitStatus } from "./LocalCommitStatus.js";
|
|
|
8
8
|
import { Main } from "./Main.js";
|
|
9
9
|
import { Output } from "./Output.js";
|
|
10
10
|
import { Providers } from "./Providers.js";
|
|
11
|
+
import { RebaseCheck } from "./RebaseCheck.js";
|
|
11
12
|
import { Store } from "./Store.js";
|
|
12
13
|
export function App() {
|
|
13
14
|
const actions = Store.useActions();
|
|
@@ -27,10 +28,15 @@ export function App() {
|
|
|
27
28
|
return (React.createElement(Providers, null,
|
|
28
29
|
React.createElement(Debug, null),
|
|
29
30
|
React.createElement(Output, null),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
React.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose || argv.update, timeoutMs: argv.update ? 30 * 1000 : 2 * 1000, onOutput: actions.output, onDone: () => {
|
|
32
|
+
if (argv.update) {
|
|
33
|
+
actions.exit(0);
|
|
34
|
+
}
|
|
35
|
+
} },
|
|
36
|
+
React.createElement(RebaseCheck, null,
|
|
37
|
+
React.createElement(DependencyCheck, null,
|
|
38
|
+
!argv.verbose ? null : React.createElement(GithubApiError, null),
|
|
39
|
+
React.createElement(GatherMetadata, null,
|
|
40
|
+
React.createElement(LocalCommitStatus, null,
|
|
41
|
+
React.createElement(Main, null))))))));
|
|
36
42
|
}
|
package/dist/app/AutoUpdate.js
CHANGED
|
@@ -5,12 +5,12 @@ import * as Ink from "ink";
|
|
|
5
5
|
import { cli } from "../core/cli.js";
|
|
6
6
|
import { colors } from "../core/colors.js";
|
|
7
7
|
import { fetch_json } from "../core/fetch_json.js";
|
|
8
|
+
import { is_finite_value } from "../core/is_finite_value.js";
|
|
8
9
|
import { read_json } from "../core/read_json.js";
|
|
9
10
|
import { semver_compare } from "../core/semver_compare.js";
|
|
10
11
|
import { sleep } from "../core/sleep.js";
|
|
11
12
|
import { Brackets } from "./Brackets.js";
|
|
12
13
|
import { FormatText } from "./FormatText.js";
|
|
13
|
-
import { Parens } from "./Parens.js";
|
|
14
14
|
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
15
15
|
function reducer(state, patch) {
|
|
16
16
|
return { ...state, ...patch };
|
|
@@ -41,31 +41,33 @@ export function AutoUpdate(props) {
|
|
|
41
41
|
let latest_version = null;
|
|
42
42
|
async function auto_update() {
|
|
43
43
|
if (props_ref.current.verbose) {
|
|
44
|
-
handle_output(React.createElement(Ink.Text, { key: "init"
|
|
44
|
+
handle_output(React.createElement(Ink.Text, { key: "init" }, "Checking for latest version..."));
|
|
45
45
|
}
|
|
46
|
-
const timeout_ms =
|
|
46
|
+
const timeout_ms = is_finite_value(props.timeoutMs)
|
|
47
|
+
? props.timeoutMs
|
|
48
|
+
: 2 * 1000;
|
|
47
49
|
const npm_json = await Promise.race([
|
|
48
50
|
fetch_json(`https://registry.npmjs.org/${props.name}`),
|
|
49
51
|
sleep(timeout_ms).then(() => {
|
|
50
|
-
throw new Error("
|
|
52
|
+
throw new Error("Timeout");
|
|
51
53
|
}),
|
|
52
54
|
]);
|
|
53
55
|
latest_version = npm_json?.["dist-tags"]?.latest;
|
|
54
56
|
if (!latest_version) {
|
|
55
|
-
throw new Error("
|
|
57
|
+
throw new Error("Unable to retrieve latest version from npm");
|
|
56
58
|
}
|
|
57
59
|
const script_dir = path.dirname(fs.realpathSync(process.argv[1]));
|
|
58
60
|
const package_json_path = path.join(script_dir, "..", "package.json");
|
|
59
61
|
const package_json = read_json(package_json_path);
|
|
60
62
|
if (!package_json) {
|
|
61
63
|
// unable to find read package.json, skip auto update
|
|
62
|
-
throw new Error(`
|
|
64
|
+
throw new Error(`Unable to find read package.json [${package_json_path}]`);
|
|
63
65
|
}
|
|
64
66
|
local_version = package_json.version;
|
|
65
67
|
if (props_ref.current.verbose) {
|
|
66
|
-
handle_output(React.createElement(FormatText, { key: "versions", wrapper: React.createElement(Ink.Text,
|
|
68
|
+
handle_output(React.createElement(FormatText, { key: "versions", wrapper: React.createElement(Ink.Text, null), message: "Auto update found latest version {latest_version} and current local version {local_version}", values: {
|
|
67
69
|
latest_version: React.createElement(Brackets, null, latest_version),
|
|
68
|
-
local_version: React.createElement(
|
|
70
|
+
local_version: React.createElement(Brackets, null, local_version),
|
|
69
71
|
} }));
|
|
70
72
|
}
|
|
71
73
|
const semver_result = semver_compare(latest_version, local_version);
|
|
@@ -88,8 +90,11 @@ export function AutoUpdate(props) {
|
|
|
88
90
|
patch({ status, error, local_version, latest_version });
|
|
89
91
|
onError(error);
|
|
90
92
|
if (props_ref.current.verbose) {
|
|
91
|
-
handle_output(React.createElement(Ink.Text, { key: "error",
|
|
93
|
+
handle_output(React.createElement(Ink.Text, { key: "error", color: colors.red }, error?.message));
|
|
92
94
|
}
|
|
95
|
+
})
|
|
96
|
+
.finally(() => {
|
|
97
|
+
props.onDone?.();
|
|
93
98
|
});
|
|
94
99
|
}, []);
|
|
95
100
|
const status = (function render_status() {
|
|
@@ -105,7 +110,7 @@ export function AutoUpdate(props) {
|
|
|
105
110
|
patch({ status: "install" });
|
|
106
111
|
await cli(`npm install -g ${props.name}@latest`);
|
|
107
112
|
patch({ status: "exit" });
|
|
108
|
-
handle_output(React.createElement(Ink.Text, { key: "done"
|
|
113
|
+
handle_output(React.createElement(Ink.Text, { key: "done" }, "Auto update done."));
|
|
109
114
|
}, onNo: () => {
|
|
110
115
|
patch({ status: "done" });
|
|
111
116
|
} }));
|
|
@@ -16,6 +16,10 @@ export function DependencyCheck(props) {
|
|
|
16
16
|
"Checking ",
|
|
17
17
|
React.createElement(Command, null, "git"),
|
|
18
18
|
" install..."), function: async () => {
|
|
19
|
+
// await Promise.all([
|
|
20
|
+
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
21
|
+
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
22
|
+
// ]);
|
|
19
23
|
if (is_command_available("git")) {
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
@@ -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
|
+
};
|
|
@@ -53,11 +53,22 @@ async function run() {
|
|
|
53
53
|
commit_message: React.createElement(Brackets, null, commit.message),
|
|
54
54
|
} }));
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
|
|
57
|
+
await cli(`git apply ${PATCH_FILE}`);
|
|
58
|
+
await cli(`rm ${PATCH_FILE}`);
|
|
59
|
+
await cli(`git add --all`);
|
|
60
|
+
let new_message;
|
|
61
|
+
if (commit.branch_id) {
|
|
62
|
+
new_message = await Metadata.write(commit.message, commit.branch_id);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
new_message = commit.message;
|
|
66
|
+
}
|
|
67
|
+
const git_commit_comand = [`git commit -m "${new_message}"`];
|
|
57
68
|
if (argv.verify === false) {
|
|
58
|
-
|
|
69
|
+
git_commit_comand.push("--no-verify");
|
|
59
70
|
}
|
|
60
|
-
await cli(
|
|
71
|
+
await cli(git_commit_comand);
|
|
61
72
|
if (commit.branch_id && !commit_pr) {
|
|
62
73
|
if (actions.isDebug()) {
|
|
63
74
|
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Cleaning up unused group {group}", values: {
|
|
@@ -95,6 +106,11 @@ async function run() {
|
|
|
95
106
|
// trying to use `await cli(...)` here will silently fail since
|
|
96
107
|
// all children processes receive the SIGINT signal
|
|
97
108
|
const spawn_options = { ignoreExitCode: true };
|
|
109
|
+
// always clean up any patch files
|
|
110
|
+
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
111
|
+
// always hard reset to allow subsequent checkout
|
|
112
|
+
// if there are files checkout will fail and cascade fail subsequent commands
|
|
113
|
+
cli.sync(`git reset --hard`, spawn_options);
|
|
98
114
|
// always put self back in original branch
|
|
99
115
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
100
116
|
// ...and cleanup temporary branch
|
|
@@ -119,3 +135,4 @@ async function run() {
|
|
|
119
135
|
actions.exit(6);
|
|
120
136
|
}
|
|
121
137
|
}
|
|
138
|
+
const PATCH_FILE = "mypatch.patch";
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -57,15 +57,16 @@ async function run(props) {
|
|
|
57
57
|
const selected_url = get_group_url(group);
|
|
58
58
|
// cherry-pick and amend commits one by one
|
|
59
59
|
for (const commit of group.commits) {
|
|
60
|
-
|
|
60
|
+
await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
|
|
61
|
+
await cli(`git apply ${PATCH_FILE}`);
|
|
62
|
+
await cli(`rm ${PATCH_FILE}`);
|
|
63
|
+
await cli(`git add --all`);
|
|
64
|
+
const new_message = await Metadata.write(commit.message, group.id);
|
|
65
|
+
const git_commit_comand = [`git commit -m "${new_message}"`];
|
|
61
66
|
if (argv.verify === false) {
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
await cli(git_cherry_pick_command);
|
|
65
|
-
if (commit.branch_id !== group.id) {
|
|
66
|
-
const new_message = await Metadata.write(commit.message, group.id);
|
|
67
|
-
await cli(`git commit --amend -m "${new_message}"`);
|
|
67
|
+
git_commit_comand.push("--no-verify");
|
|
68
68
|
}
|
|
69
|
+
await cli(git_commit_comand);
|
|
69
70
|
}
|
|
70
71
|
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
|
|
71
72
|
group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
|
|
@@ -164,6 +165,11 @@ async function run(props) {
|
|
|
164
165
|
// trying to use `await cli(...)` here will silently fail since
|
|
165
166
|
// all children processes receive the SIGINT signal
|
|
166
167
|
const spawn_options = { ignoreExitCode: true };
|
|
168
|
+
// always clean up any patch files
|
|
169
|
+
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
170
|
+
// always hard reset to allow subsequent checkout
|
|
171
|
+
// if there are files checkout will fail and cascade fail subsequent commands
|
|
172
|
+
cli.sync(`git reset --hard`, spawn_options);
|
|
167
173
|
// always put self back in original branch
|
|
168
174
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
169
175
|
// ...and cleanup temporary branch
|
|
@@ -189,3 +195,4 @@ async function run(props) {
|
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
const get_group_url = (group) => group.pr?.url || group.id;
|
|
198
|
+
const PATCH_FILE = "mypatch.patch";
|
package/dist/app/Output.js
CHANGED
|
@@ -3,7 +3,17 @@ import * as Ink from "ink";
|
|
|
3
3
|
import { Store } from "./Store.js";
|
|
4
4
|
export function Output() {
|
|
5
5
|
const output = Store.useState((state) => state.output);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const pending_output = Store.useState((state) => state.pending_output);
|
|
7
|
+
const pending_output_items = Object.values(pending_output);
|
|
8
|
+
return (React.createElement(React.Fragment, null,
|
|
9
|
+
React.createElement(Ink.Static, { items: output }, (node, i) => {
|
|
10
|
+
return React.createElement(Ink.Box, { key: i }, node);
|
|
11
|
+
}),
|
|
12
|
+
pending_output_items.map((node_list, i) => {
|
|
13
|
+
return (React.createElement(Ink.Box, { key: i },
|
|
14
|
+
React.createElement(Ink.Text, null, node_list.map((text, j) => {
|
|
15
|
+
return (React.createElement(React.Fragment, { key: j },
|
|
16
|
+
React.createElement(Ink.Text, null, text)));
|
|
17
|
+
}))));
|
|
18
|
+
})));
|
|
9
19
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import * as Ink from "ink";
|
|
5
|
+
import { cli } from "../core/cli.js";
|
|
6
|
+
import { colors } from "../core/colors.js";
|
|
7
|
+
import { invariant } from "../core/invariant.js";
|
|
8
|
+
import { Await } from "./Await.js";
|
|
9
|
+
import { Store } from "./Store.js";
|
|
10
|
+
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
11
|
+
function reducer(state, patch) {
|
|
12
|
+
return { ...state, ...patch };
|
|
13
|
+
}
|
|
14
|
+
export function RebaseCheck(props) {
|
|
15
|
+
const actions = Store.useActions();
|
|
16
|
+
const argv = Store.useState((state) => state.argv);
|
|
17
|
+
invariant(argv, "argv must exist");
|
|
18
|
+
const [state, patch] = React.useReducer(reducer, {
|
|
19
|
+
status: "init",
|
|
20
|
+
});
|
|
21
|
+
switch (state.status) {
|
|
22
|
+
case "done":
|
|
23
|
+
return props.children;
|
|
24
|
+
case "prompt":
|
|
25
|
+
return (React.createElement(YesNoPrompt, { message: React.createElement(Ink.Text, { color: colors.yellow }, "Rebase detected, would you like to abort it?"), onYes: async () => {
|
|
26
|
+
await cli(`git rebase --abort`);
|
|
27
|
+
patch({ status: "done" });
|
|
28
|
+
}, onNo: async () => {
|
|
29
|
+
actions.exit(0);
|
|
30
|
+
} }));
|
|
31
|
+
default:
|
|
32
|
+
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow }, "Checking for rebase..."), function: rebase_check }));
|
|
33
|
+
}
|
|
34
|
+
async function rebase_check() {
|
|
35
|
+
const actions = Store.getState().actions;
|
|
36
|
+
const argv = Store.getState().argv;
|
|
37
|
+
invariant(argv, "argv must exist");
|
|
38
|
+
try {
|
|
39
|
+
const repo_root = (await cli(`git rev-parse --absolute-git-dir`)).stdout;
|
|
40
|
+
let is_rebase = false;
|
|
41
|
+
is_rebase ||= fs.existsSync(path.join(repo_root, "rebase-apply"));
|
|
42
|
+
is_rebase ||= fs.existsSync(path.join(repo_root, "rebase-merge"));
|
|
43
|
+
const status = is_rebase ? "prompt" : "done";
|
|
44
|
+
patch({ status });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
actions.error("Unable to check for rebase.");
|
|
48
|
+
if (err instanceof Error) {
|
|
49
|
+
if (actions.isDebug()) {
|
|
50
|
+
actions.error(err.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
actions.exit(9);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/app/Store.js
CHANGED
|
@@ -18,11 +18,13 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
18
18
|
commit_map: null,
|
|
19
19
|
step: "loading",
|
|
20
20
|
output: [],
|
|
21
|
+
pending_output: {},
|
|
21
22
|
pr: {},
|
|
22
23
|
actions: {
|
|
23
24
|
exit(code, clear = true) {
|
|
24
25
|
set((state) => {
|
|
25
|
-
|
|
26
|
+
const node = React.createElement(Exit, { clear: clear, code: code });
|
|
27
|
+
state.mutate.output(state, { node });
|
|
26
28
|
});
|
|
27
29
|
},
|
|
28
30
|
clear() {
|
|
@@ -33,28 +35,37 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
33
35
|
},
|
|
34
36
|
newline() {
|
|
35
37
|
set((state) => {
|
|
36
|
-
|
|
38
|
+
const node = "";
|
|
39
|
+
state.mutate.output(state, { node });
|
|
37
40
|
});
|
|
38
41
|
},
|
|
39
42
|
json(value) {
|
|
40
43
|
set((state) => {
|
|
41
|
-
|
|
44
|
+
const node = JSON.stringify(value, null, 2);
|
|
45
|
+
state.mutate.output(state, { node });
|
|
42
46
|
});
|
|
43
47
|
},
|
|
44
48
|
error(message) {
|
|
45
49
|
set((state) => {
|
|
46
|
-
|
|
50
|
+
const node = React.createElement(Ink.Text, { color: colors.red }, message);
|
|
51
|
+
state.mutate.output(state, { node });
|
|
47
52
|
});
|
|
48
53
|
},
|
|
49
54
|
output(node) {
|
|
50
55
|
set((state) => {
|
|
51
|
-
state.mutate.output(state, node);
|
|
56
|
+
state.mutate.output(state, { node });
|
|
52
57
|
});
|
|
53
58
|
},
|
|
54
|
-
debug(node) {
|
|
59
|
+
debug(node, id) {
|
|
55
60
|
if (get().actions.isDebug()) {
|
|
61
|
+
const debug = true;
|
|
56
62
|
set((state) => {
|
|
57
|
-
|
|
63
|
+
if (id) {
|
|
64
|
+
state.mutate.pending_output(state, { id, node, debug });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
state.mutate.output(state, { node, debug });
|
|
68
|
+
}
|
|
58
69
|
});
|
|
59
70
|
}
|
|
60
71
|
},
|
|
@@ -74,15 +85,36 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
74
85
|
},
|
|
75
86
|
},
|
|
76
87
|
mutate: {
|
|
77
|
-
output(state,
|
|
78
|
-
switch (typeof node) {
|
|
88
|
+
output(state, args) {
|
|
89
|
+
switch (typeof args.node) {
|
|
79
90
|
case "boolean":
|
|
80
91
|
case "number":
|
|
81
92
|
case "string":
|
|
82
|
-
state.output.push(React.createElement(Ink.Text,
|
|
93
|
+
state.output.push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
|
|
83
94
|
return;
|
|
84
95
|
}
|
|
85
|
-
state.output.push(node);
|
|
96
|
+
state.output.push(args.node);
|
|
97
|
+
},
|
|
98
|
+
pending_output(state, args) {
|
|
99
|
+
const { id } = args;
|
|
100
|
+
if (!id) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!state.pending_output[id]) {
|
|
104
|
+
state.pending_output[id] = [];
|
|
105
|
+
}
|
|
106
|
+
switch (typeof args.node) {
|
|
107
|
+
case "boolean":
|
|
108
|
+
case "number":
|
|
109
|
+
case "string": {
|
|
110
|
+
state.pending_output[id].push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
state.pending_output[id].push(args.node);
|
|
115
|
+
},
|
|
116
|
+
end_pending_output(state, id) {
|
|
117
|
+
delete state.pending_output[id];
|
|
86
118
|
},
|
|
87
119
|
},
|
|
88
120
|
select: {
|
package/dist/command.js
CHANGED
|
@@ -26,6 +26,12 @@ export async function command() {
|
|
|
26
26
|
alias: ["v"],
|
|
27
27
|
default: false,
|
|
28
28
|
description: "Print more detailed logs for debugging internals",
|
|
29
|
+
})
|
|
30
|
+
.option("update", {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
alias: ["u"],
|
|
33
|
+
default: false,
|
|
34
|
+
description: "Check for updates",
|
|
29
35
|
})
|
|
30
36
|
.option("branch", {
|
|
31
37
|
type: "string",
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
export function write(args) {
|
|
2
2
|
const stack_list = [];
|
|
3
|
-
|
|
3
|
+
const digits = String(args.pr_url_list.length).length;
|
|
4
|
+
for (let i = 0; i < args.pr_url_list.length; i++) {
|
|
5
|
+
const pr_url = args.pr_url_list[i];
|
|
4
6
|
if (pr_url) {
|
|
5
7
|
const selected = args.selected_url === pr_url;
|
|
6
8
|
const icon = selected ? "👉" : "⏳";
|
|
7
|
-
|
|
9
|
+
const num = String(i + 1).padStart(digits, "0");
|
|
10
|
+
stack_list.push(`- ${icon} \`${num}\` ${pr_url}`);
|
|
8
11
|
}
|
|
9
12
|
}
|
|
13
|
+
// reverse order of pr list to match the order of git stack
|
|
14
|
+
stack_list.reverse();
|
|
10
15
|
let stack_table;
|
|
11
16
|
if (stack_list.length > 1) {
|
|
12
17
|
stack_table = TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
|
package/dist/core/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as child from "node:child_process";
|
|
2
2
|
import { Store } from "../app/Store.js";
|
|
3
|
+
let i = 0;
|
|
3
4
|
export async function cli(unsafe_command, unsafe_options) {
|
|
4
|
-
const state = Store.getState();
|
|
5
5
|
const options = Object.assign({}, unsafe_options);
|
|
6
|
+
const state = Store.getState();
|
|
6
7
|
let command;
|
|
7
8
|
if (Array.isArray(unsafe_command)) {
|
|
8
9
|
command = unsafe_command.join(" ");
|
|
@@ -15,28 +16,38 @@ export async function cli(unsafe_command, unsafe_options) {
|
|
|
15
16
|
let stdout = "";
|
|
16
17
|
let stderr = "";
|
|
17
18
|
let output = "";
|
|
19
|
+
const id = `${++i}-${command}`;
|
|
20
|
+
state.actions.debug(`[start] ${command}`);
|
|
21
|
+
state.actions.debug(`[start] ${command}\n`, id);
|
|
22
|
+
function write_output(value) {
|
|
23
|
+
output += value;
|
|
24
|
+
state.actions.debug(value, id);
|
|
25
|
+
}
|
|
18
26
|
childProcess.stdout?.on("data", (data) => {
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
const value = String(data);
|
|
28
|
+
stdout += value;
|
|
29
|
+
write_output(value);
|
|
21
30
|
});
|
|
22
31
|
childProcess.stderr?.on("data", (data) => {
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const value = String(data);
|
|
33
|
+
stderr += value;
|
|
34
|
+
write_output(value);
|
|
25
35
|
});
|
|
26
|
-
childProcess.on("close", (
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
childProcess.on("close", (unsafe_code) => {
|
|
37
|
+
const result = {
|
|
38
|
+
command,
|
|
39
|
+
code: unsafe_code || 0,
|
|
40
|
+
stdout: stdout.trimEnd(),
|
|
41
|
+
stderr: stderr.trimEnd(),
|
|
42
|
+
output: output.trimEnd(),
|
|
43
|
+
};
|
|
44
|
+
state.actions.set((state) => state.mutate.end_pending_output(state, id));
|
|
45
|
+
state.actions.debug(`[end] ${command} (${result.code})`);
|
|
46
|
+
state.actions.debug(result.output);
|
|
47
|
+
if (!options.ignoreExitCode && result.code !== 0) {
|
|
48
|
+
reject(new Error(`[${command}] (${result.code})`));
|
|
29
49
|
}
|
|
30
50
|
else {
|
|
31
|
-
const result = {
|
|
32
|
-
command,
|
|
33
|
-
code: code || 0,
|
|
34
|
-
stdout: stdout.trimEnd(),
|
|
35
|
-
stderr: stderr.trimEnd(),
|
|
36
|
-
output: output.trimEnd(),
|
|
37
|
-
};
|
|
38
|
-
state.actions.debug(`$ ${command}`);
|
|
39
|
-
state.actions.debug(result.output);
|
|
40
51
|
resolve(result);
|
|
41
52
|
}
|
|
42
53
|
});
|
|
@@ -45,12 +56,31 @@ export async function cli(unsafe_command, unsafe_options) {
|
|
|
45
56
|
});
|
|
46
57
|
});
|
|
47
58
|
}
|
|
48
|
-
cli.sync = function cli_sync(
|
|
59
|
+
cli.sync = function cli_sync(unsafe_command, unsafe_options) {
|
|
49
60
|
const options = Object.assign({}, unsafe_options);
|
|
61
|
+
const state = Store.getState();
|
|
62
|
+
let command;
|
|
63
|
+
if (Array.isArray(unsafe_command)) {
|
|
64
|
+
command = unsafe_command.join(" ");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
command = unsafe_command;
|
|
68
|
+
}
|
|
69
|
+
state.actions.debug(`[start] ${command}`);
|
|
50
70
|
const spawn_return = child.spawnSync("sh", ["-c", command], options);
|
|
51
71
|
const stdout = String(spawn_return.stdout);
|
|
52
72
|
const stderr = String(spawn_return.stderr);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
const result = {
|
|
74
|
+
command,
|
|
75
|
+
code: spawn_return.status || 0,
|
|
76
|
+
stdout,
|
|
77
|
+
stderr,
|
|
78
|
+
output: [stdout, stderr].join(""),
|
|
79
|
+
};
|
|
80
|
+
state.actions.debug(`[end] ${command} (${result.code})`);
|
|
81
|
+
state.actions.debug(result.output);
|
|
82
|
+
if (!options.ignoreExitCode && result.code !== 0) {
|
|
83
|
+
throw new Error(`[${command}] (${result.code})`);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
56
86
|
};
|