git-stack-cli 0.7.6 → 0.8.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/dist/app/App.js +12 -6
- package/dist/app/AutoUpdate.js +15 -10
- package/dist/app/GatherMetadata copy.js +37 -4
- package/dist/app/LocalMergeRebase.js +3 -0
- package/dist/app/ManualRebase.js +3 -0
- package/dist/app/RebaseCheck.js +56 -0
- package/dist/command.js +6 -0
- package/dist/core/StackSummaryTable.js +7 -2
- package/dist/core/cli.js +30 -11
- 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
|
} }));
|
|
@@ -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
|
+
};
|
|
@@ -108,6 +108,9 @@ async function run() {
|
|
|
108
108
|
const spawn_options = { ignoreExitCode: true };
|
|
109
109
|
// always clean up any patch files
|
|
110
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);
|
|
111
114
|
// always put self back in original branch
|
|
112
115
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
113
116
|
// ...and cleanup temporary branch
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -167,6 +167,9 @@ async function run(props) {
|
|
|
167
167
|
const spawn_options = { ignoreExitCode: true };
|
|
168
168
|
// always clean up any patch files
|
|
169
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);
|
|
170
173
|
// always put self back in original branch
|
|
171
174
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
172
175
|
// ...and cleanup temporary branch
|
|
@@ -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/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
|
@@ -2,8 +2,8 @@ import * as child from "node:child_process";
|
|
|
2
2
|
import { Store } from "../app/Store.js";
|
|
3
3
|
let i = 0;
|
|
4
4
|
export async function cli(unsafe_command, unsafe_options) {
|
|
5
|
-
const state = Store.getState();
|
|
6
5
|
const options = Object.assign({}, unsafe_options);
|
|
6
|
+
const state = Store.getState();
|
|
7
7
|
let command;
|
|
8
8
|
if (Array.isArray(unsafe_command)) {
|
|
9
9
|
command = unsafe_command.join(" ");
|
|
@@ -18,7 +18,7 @@ export async function cli(unsafe_command, unsafe_options) {
|
|
|
18
18
|
let output = "";
|
|
19
19
|
const id = `${++i}-${command}`;
|
|
20
20
|
state.actions.debug(`[start] ${command}`);
|
|
21
|
-
state.actions.debug(`[
|
|
21
|
+
state.actions.debug(`[⏳] ${command}\n`, id);
|
|
22
22
|
function write_output(value) {
|
|
23
23
|
output += value;
|
|
24
24
|
state.actions.debug(value, id);
|
|
@@ -33,19 +33,19 @@ export async function cli(unsafe_command, unsafe_options) {
|
|
|
33
33
|
stderr += value;
|
|
34
34
|
write_output(value);
|
|
35
35
|
});
|
|
36
|
-
childProcess.on("close", (
|
|
36
|
+
childProcess.on("close", (unsafe_code) => {
|
|
37
37
|
const result = {
|
|
38
38
|
command,
|
|
39
|
-
code:
|
|
39
|
+
code: unsafe_code || 0,
|
|
40
40
|
stdout: stdout.trimEnd(),
|
|
41
41
|
stderr: stderr.trimEnd(),
|
|
42
42
|
output: output.trimEnd(),
|
|
43
43
|
};
|
|
44
44
|
state.actions.set((state) => state.mutate.end_pending_output(state, id));
|
|
45
|
-
state.actions.debug(`[end] ${command}`);
|
|
45
|
+
state.actions.debug(`[end] ${command} (${result.code})`);
|
|
46
46
|
state.actions.debug(result.output);
|
|
47
|
-
if (!options.ignoreExitCode && code !== 0) {
|
|
48
|
-
reject(new Error(`[${command}] (${code})`));
|
|
47
|
+
if (!options.ignoreExitCode && result.code !== 0) {
|
|
48
|
+
reject(new Error(`[${command}] (${result.code})`));
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
51
|
resolve(result);
|
|
@@ -56,12 +56,31 @@ export async function cli(unsafe_command, unsafe_options) {
|
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
cli.sync = function cli_sync(
|
|
59
|
+
cli.sync = function cli_sync(unsafe_command, unsafe_options) {
|
|
60
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}`);
|
|
61
70
|
const spawn_return = child.spawnSync("sh", ["-c", command], options);
|
|
62
71
|
const stdout = String(spawn_return.stdout);
|
|
63
72
|
const stderr = String(spawn_return.stderr);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
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;
|
|
67
86
|
};
|