git-stack-cli 0.8.2 → 0.8.3
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/__fixtures__/metadata.json +186 -0
- package/dist/app/App copy.js +30 -0
- package/dist/app/ArgCheck.js +21 -0
- package/dist/app/Brackets copy.js +10 -0
- package/dist/app/Counter.js +19 -0
- package/dist/app/GatherMetadata copy.js +91 -0
- package/dist/app/GatherMetadata.js +7 -0
- package/dist/app/InitMetadata.js +14 -0
- package/dist/app/Input.js +15 -0
- package/dist/app/KeepAlive.js +11 -0
- package/dist/app/LocalMergeRebase.js +5 -1
- package/dist/app/Main copy.js +200 -0
- package/dist/app/ManualRebase copy.js +127 -0
- package/dist/app/ManualRebase.js +5 -1
- package/dist/app/MultiSelect copy.js +76 -0
- package/dist/app/NPMAutoUpdate.js +34 -0
- package/dist/app/Parens copy.js +9 -0
- package/dist/app/PostRebaseStatus copy.js +23 -0
- package/dist/app/PreSelectCommitRanges copy.js +21 -0
- package/dist/app/SelectCommitRange.js +1 -0
- package/dist/app/Status copy.js +46 -0
- package/dist/app/Url copy.js +6 -0
- package/dist/app/YesNoPrompt copy.js +24 -0
- package/dist/cli.js +9 -0
- package/dist/core/Metadata copy.js +37 -0
- package/dist/core/StackTable.js +38 -0
- package/dist/core/SummaryTable.js +38 -0
- package/dist/core/ZustandStore.js +23 -0
- package/dist/core/cli copy.js +44 -0
- package/dist/core/color.js +83 -0
- package/dist/core/dependency_check.js +27 -0
- package/dist/core/exit.js +4 -0
- package/dist/core/get_commit_metadata.js +61 -0
- package/dist/core/id.js +61 -0
- package/dist/core/invariant copy.js +5 -0
- package/dist/core/isFiniteValue.js +3 -0
- package/dist/core/is_dev.js +1 -0
- package/dist/core/readJson.js +3 -0
- package/dist/core/serialize_json.js +17 -0
- package/dist/core/sleep copy.js +3 -0
- package/dist/main copy.js +266 -0
- package/dist/main.backup.js +266 -0
- package/dist/main.js +265 -0
- package/package.json +6 -4
- /package/dist/app/{Main.js → main.js} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
import * as CommitMetadata from "../core/CommitMetadata.js";
|
|
4
|
+
import * as Metadata from "../core/Metadata.js";
|
|
5
|
+
import { cli } from "../core/cli.js";
|
|
6
|
+
import * as github from "../core/github.js";
|
|
7
|
+
import { invariant } from "../core/invariant.js";
|
|
8
|
+
import { short_id } from "../core/short_id.js";
|
|
9
|
+
import { Await } from "./Await.js";
|
|
10
|
+
import { Brackets } from "./Brackets.js";
|
|
11
|
+
import { Store } from "./Store.js";
|
|
12
|
+
export function ManualRebase(props) {
|
|
13
|
+
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
|
|
14
|
+
}
|
|
15
|
+
async function run(props) {
|
|
16
|
+
const state = Store.getState();
|
|
17
|
+
const actions = state.actions;
|
|
18
|
+
const branch_name = state.branch_name;
|
|
19
|
+
const merge_base = state.merge_base;
|
|
20
|
+
const commit_map = state.commit_map;
|
|
21
|
+
invariant(branch_name, "branch_name must exist");
|
|
22
|
+
invariant(merge_base, "merge_base must exist");
|
|
23
|
+
invariant(commit_map, "commit_map must exist");
|
|
24
|
+
// always listen for SIGINT event and restore git state
|
|
25
|
+
process.once("SIGINT", handle_exit);
|
|
26
|
+
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
27
|
+
const commit_range = await CommitMetadata.range(commit_map);
|
|
28
|
+
// reverse commit list so that we can cherry-pick in order
|
|
29
|
+
commit_range.group_list.reverse();
|
|
30
|
+
let rebase_merge_base = merge_base;
|
|
31
|
+
let rebase_group_index = 0;
|
|
32
|
+
for (let i = 0; i < commit_range.group_list.length; i++) {
|
|
33
|
+
const group = commit_range.group_list[i];
|
|
34
|
+
if (!group.dirty) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (i > 0) {
|
|
38
|
+
const last_group = commit_range.group_list[i - 1];
|
|
39
|
+
const last_commit = last_group.commits[last_group.commits.length - 1];
|
|
40
|
+
rebase_merge_base = last_commit.sha;
|
|
41
|
+
rebase_group_index = i;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// create temporary branch based on merge base
|
|
47
|
+
await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
|
|
48
|
+
for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
|
|
49
|
+
const group = commit_range.group_list[i];
|
|
50
|
+
invariant(group.base, "group.base must exist");
|
|
51
|
+
// cherry-pick and amend commits one by one
|
|
52
|
+
for (const commit of group.commits) {
|
|
53
|
+
await cli(`git cherry-pick ${commit.sha}`);
|
|
54
|
+
if (commit.branch_id !== group.id) {
|
|
55
|
+
const new_message = await Metadata.write(commit.message, group.id);
|
|
56
|
+
await cli(`git commit --amend -m "${new_message}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" },
|
|
60
|
+
"Syncing ",
|
|
61
|
+
React.createElement(Brackets, null, group.pr?.title || group.id),
|
|
62
|
+
"..."));
|
|
63
|
+
if (!props.skipSync) {
|
|
64
|
+
// push to origin since github requires commit shas to line up perfectly
|
|
65
|
+
await cli(`git push -f origin HEAD:${group.id}`);
|
|
66
|
+
if (group.pr) {
|
|
67
|
+
// ensure base matches pr in github
|
|
68
|
+
await github.pr_base(group.id, group.base);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// delete local group branch if leftover
|
|
72
|
+
await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
|
|
73
|
+
// move to temporary branch for creating pr
|
|
74
|
+
await cli(`git checkout -b ${group.id}`);
|
|
75
|
+
// create pr in github
|
|
76
|
+
await github.pr_create(group.id, group.base);
|
|
77
|
+
// move back to temp branch
|
|
78
|
+
await cli(`git checkout ${temp_branch_name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// after all commits have been cherry-picked and amended
|
|
83
|
+
// move the branch pointer to the newly created temporary branch
|
|
84
|
+
// now we are in locally in sync with github and on the original branch
|
|
85
|
+
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
86
|
+
restore_git();
|
|
87
|
+
actions.set((state) => {
|
|
88
|
+
state.step = "post-rebase-status";
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
actions.output(React.createElement(Ink.Text, { color: "#ef4444" }, "Error during rebase."));
|
|
93
|
+
if (err instanceof Error) {
|
|
94
|
+
actions.debug(React.createElement(Ink.Text, { color: "#ef4444" }, err.message));
|
|
95
|
+
}
|
|
96
|
+
handle_exit();
|
|
97
|
+
}
|
|
98
|
+
// cleanup git operations if cancelled during manual rebase
|
|
99
|
+
function restore_git() {
|
|
100
|
+
// signint handler MUST run synchronously
|
|
101
|
+
// trying to use `await cli(...)` here will silently fail since
|
|
102
|
+
// all children processes receive the SIGINT signal
|
|
103
|
+
const spawn_options = { ignoreExitCode: true };
|
|
104
|
+
// always put self back in original branch
|
|
105
|
+
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
106
|
+
// ...and cleanup temporary branch
|
|
107
|
+
cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
|
|
108
|
+
if (commit_range) {
|
|
109
|
+
// ...and cleanup pr group branches
|
|
110
|
+
for (const group of commit_range.group_list) {
|
|
111
|
+
cli.sync(`git branch -D ${group.id}`, spawn_options);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function handle_exit() {
|
|
116
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
117
|
+
"Restoring ",
|
|
118
|
+
React.createElement(Brackets, null, branch_name),
|
|
119
|
+
"..."));
|
|
120
|
+
restore_git();
|
|
121
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
122
|
+
"Restored ",
|
|
123
|
+
React.createElement(Brackets, null, branch_name),
|
|
124
|
+
"."));
|
|
125
|
+
actions.exit(5);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -60,9 +60,13 @@ async function run(props) {
|
|
|
60
60
|
const selected_url = get_group_url(group);
|
|
61
61
|
// cherry-pick and amend commits one by one
|
|
62
62
|
for (const commit of group.commits) {
|
|
63
|
+
// ensure clean base to avoid conflicts when applying patch
|
|
64
|
+
await cli(`git clean -fd`);
|
|
65
|
+
// create, apply and cleanup patch
|
|
63
66
|
await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
|
|
64
67
|
await cli(`git apply ${PATCH_FILE}`);
|
|
65
68
|
await cli(`rm ${PATCH_FILE}`);
|
|
69
|
+
// add all changes to stage
|
|
66
70
|
await cli(`git add --all`);
|
|
67
71
|
const new_message = await Metadata.write(commit.message, group.id);
|
|
68
72
|
const git_commit_comand = [`git commit -m "${new_message}"`];
|
|
@@ -199,4 +203,4 @@ async function run(props) {
|
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
const get_group_url = (group) => group.pr?.url || group.id;
|
|
202
|
-
const PATCH_FILE = "
|
|
206
|
+
const PATCH_FILE = "git-stack-cli-patch.patch";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
import { clamp } from "../core/clamp.js";
|
|
4
|
+
export function MultiSelect(props) {
|
|
5
|
+
const [selected, select] = React.useReducer((state, value) => {
|
|
6
|
+
const next = new Set(state);
|
|
7
|
+
if (next.has(value)) {
|
|
8
|
+
next.delete(value);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
next.add(value);
|
|
12
|
+
}
|
|
13
|
+
return next;
|
|
14
|
+
}, new Set());
|
|
15
|
+
// clamp index to keep in item range
|
|
16
|
+
const [index, set_index] = React.useReducer((_, value) => {
|
|
17
|
+
return clamp(value, 0, props.items.length - 1);
|
|
18
|
+
}, 0);
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
const item = props.items[index];
|
|
21
|
+
const selected_list = Array.from(selected);
|
|
22
|
+
const list = selected_list.map((index) => props.items[index]);
|
|
23
|
+
props.onSelect(item, list);
|
|
24
|
+
}, [selected]);
|
|
25
|
+
Ink.useInput((_input, key) => {
|
|
26
|
+
if (key.return) {
|
|
27
|
+
return select(index);
|
|
28
|
+
}
|
|
29
|
+
if (key.upArrow) {
|
|
30
|
+
return set_index(index - 1);
|
|
31
|
+
}
|
|
32
|
+
if (key.downArrow) {
|
|
33
|
+
return set_index(index + 1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return (React.createElement(Ink.Box, { flexDirection: "column" }, props.items.map((item, i) => {
|
|
37
|
+
const active = i === index;
|
|
38
|
+
return (React.createElement(ItemRow, { key: item.label, label: item.label, active: active, selected: selected.has(i) }));
|
|
39
|
+
})));
|
|
40
|
+
}
|
|
41
|
+
function Radio(props) {
|
|
42
|
+
let display;
|
|
43
|
+
let color;
|
|
44
|
+
if (props.selected) {
|
|
45
|
+
// display = "✓";
|
|
46
|
+
display = "◉";
|
|
47
|
+
color = "green";
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// display = " ";
|
|
51
|
+
display = "◯";
|
|
52
|
+
color = "";
|
|
53
|
+
}
|
|
54
|
+
return (React.createElement(Ink.Text, { bold: props.selected, color: color }, display));
|
|
55
|
+
}
|
|
56
|
+
function ItemRow(props) {
|
|
57
|
+
let color;
|
|
58
|
+
let underline;
|
|
59
|
+
let dimColor;
|
|
60
|
+
if (props.active) {
|
|
61
|
+
color = "#38bdf8";
|
|
62
|
+
underline = true;
|
|
63
|
+
}
|
|
64
|
+
else if (props.selected) {
|
|
65
|
+
// color = "";
|
|
66
|
+
dimColor = false;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// color = "gray";
|
|
70
|
+
dimColor = true;
|
|
71
|
+
}
|
|
72
|
+
return (React.createElement(Ink.Box, { flexDirection: "row", gap: 1 },
|
|
73
|
+
React.createElement(Radio, { selected: props.selected }),
|
|
74
|
+
React.createElement(Ink.Box, null,
|
|
75
|
+
React.createElement(Ink.Text, { bold: props.selected, underline: underline, color: color, dimColor: dimColor, wrap: "truncate-end" }, props.label))));
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import * as Ink from "ink";
|
|
4
|
+
export function AutoUpdate(props) {
|
|
5
|
+
const props_ref = React.useRef(props);
|
|
6
|
+
props_ref.current = props;
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
async function auto_update() {
|
|
9
|
+
try {
|
|
10
|
+
const npm_res = await fetch(`https://registry.npmjs.org/${props.name}`);
|
|
11
|
+
const npm_json = await npm_res.json();
|
|
12
|
+
const latest = npm_json["dist-tags"].latest;
|
|
13
|
+
console.debug({ latest });
|
|
14
|
+
const script_path = process.argv[1];
|
|
15
|
+
const path_list = [process.argv[1]];
|
|
16
|
+
const real_path_list = path_list.map((p) => fs.realpathSync(p));
|
|
17
|
+
console.debug({ real_path_list, path_list });
|
|
18
|
+
// const winner = await Promise.race([
|
|
19
|
+
// sleep(Math.random() * 5000).then(() => "a"),
|
|
20
|
+
// sleep(Math.random() * 5000).then(() => "b"),
|
|
21
|
+
// ]);
|
|
22
|
+
// console.debug({ winner });
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.debug({ err });
|
|
26
|
+
// handled by catch below
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const onError = props_ref.current.onError || (() => { });
|
|
31
|
+
auto_update().catch(onError);
|
|
32
|
+
}, []);
|
|
33
|
+
return React.createElement(Ink.Text, { color: "yellow" }, "Checking for latest version...");
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
export function Parens(props) {
|
|
4
|
+
const color = "#38bdf8";
|
|
5
|
+
return (React.createElement(Ink.Text, null,
|
|
6
|
+
React.createElement(Ink.Text, { color: color }, "("),
|
|
7
|
+
props.children,
|
|
8
|
+
React.createElement(Ink.Text, { color: color }, ")")));
|
|
9
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
import * as CommitMetadata from "../core/CommitMetadata.js";
|
|
4
|
+
import { invariant } from "../core/invariant.js";
|
|
5
|
+
import { Await } from "./Await.js";
|
|
6
|
+
import { StatusTable } from "./StatusTable.js";
|
|
7
|
+
import { Store } from "./Store.js";
|
|
8
|
+
export function PostRebaseStatus() {
|
|
9
|
+
const argv = Store.useState((state) => state.argv);
|
|
10
|
+
invariant(argv, "argv must exist");
|
|
11
|
+
return React.createElement(Await, { fallback: null, function: run });
|
|
12
|
+
}
|
|
13
|
+
async function run() {
|
|
14
|
+
const actions = Store.getState().actions;
|
|
15
|
+
// reset github pr cache before refreshing via commit range below
|
|
16
|
+
actions.reset_pr();
|
|
17
|
+
const commit_range = await CommitMetadata.range();
|
|
18
|
+
actions.set((state) => {
|
|
19
|
+
state.commit_range = commit_range;
|
|
20
|
+
});
|
|
21
|
+
actions.output(React.createElement(StatusTable, null));
|
|
22
|
+
actions.output(React.createElement(Ink.Text, null, "\u2705 Everything up to date."));
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { invariant } from "../core/invariant.js";
|
|
3
|
+
import { Store } from "./Store.js";
|
|
4
|
+
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
5
|
+
export function PreSelectCommitRanges() {
|
|
6
|
+
const actions = Store.useActions();
|
|
7
|
+
const argv = Store.useState((state) => state.argv);
|
|
8
|
+
invariant(argv, "argv must exist");
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (argv.force) {
|
|
11
|
+
Store.setState((state) => {
|
|
12
|
+
state.step = "select-commit-ranges";
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}, [argv]);
|
|
16
|
+
return (React.createElement(YesNoPrompt, { message: "Some commits are new or outdated, would you like to select new commit ranges?", onYes: () => {
|
|
17
|
+
actions.set((state) => {
|
|
18
|
+
state.step = "select-commit-ranges";
|
|
19
|
+
});
|
|
20
|
+
}, onNo: () => actions.exit(0) }));
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
import { invariant } from "../core/invariant.js";
|
|
4
|
+
import { Await } from "./Await.js";
|
|
5
|
+
import { StatusTable } from "./StatusTable.js";
|
|
6
|
+
import { Store } from "./Store.js";
|
|
7
|
+
export function Status() {
|
|
8
|
+
const argv = Store.useState((state) => state.argv);
|
|
9
|
+
invariant(argv, "argv must exist");
|
|
10
|
+
return React.createElement(Await, { fallback: null, function: () => run({ argv }) });
|
|
11
|
+
}
|
|
12
|
+
async function run(args) {
|
|
13
|
+
const actions = Store.getState().actions;
|
|
14
|
+
const commit_range = Store.getState().commit_range;
|
|
15
|
+
invariant(commit_range, "commit_range must exist");
|
|
16
|
+
actions.output(React.createElement(StatusTable, null));
|
|
17
|
+
let needs_update = false;
|
|
18
|
+
for (const group of commit_range.group_list) {
|
|
19
|
+
if (group.dirty) {
|
|
20
|
+
needs_update = true;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (args.argv.check) {
|
|
25
|
+
actions.exit(0);
|
|
26
|
+
}
|
|
27
|
+
else if (args.argv.force) {
|
|
28
|
+
Store.setState((state) => {
|
|
29
|
+
state.step = "select-commit-ranges";
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (needs_update) {
|
|
33
|
+
Store.setState((state) => {
|
|
34
|
+
state.step = "pre-select-commit-ranges";
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
actions.newline();
|
|
39
|
+
actions.output(React.createElement(Ink.Text, null, "\u2705 Everything up to date."));
|
|
40
|
+
actions.output(React.createElement(Ink.Text, { color: "gray" },
|
|
41
|
+
React.createElement(Ink.Text, null, "Run with"),
|
|
42
|
+
React.createElement(Ink.Text, { bold: true, color: "yellow" }, ` --force `),
|
|
43
|
+
React.createElement(Ink.Text, null, "to force update all pull requests.")));
|
|
44
|
+
actions.exit(0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
export function YesNoPrompt(props) {
|
|
4
|
+
Ink.useInput((input) => {
|
|
5
|
+
const inputLower = input.toLowerCase();
|
|
6
|
+
switch (inputLower) {
|
|
7
|
+
case "n":
|
|
8
|
+
return props.onNo();
|
|
9
|
+
case "y":
|
|
10
|
+
return props.onYes();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return (React.createElement(Ink.Box, { flexDirection: "column" },
|
|
14
|
+
React.createElement(Ink.Box, null,
|
|
15
|
+
React.createElement(Ink.Text, { color: "yellow" }, props.message),
|
|
16
|
+
React.createElement(Ink.Text, null, " "),
|
|
17
|
+
React.createElement(Ink.Text, { color: "#06b6d4" },
|
|
18
|
+
"(",
|
|
19
|
+
React.createElement(Ink.Text, { color: "gray" },
|
|
20
|
+
React.createElement(Ink.Text, { color: "#22c55e", dimColor: true }, "Y"),
|
|
21
|
+
"/",
|
|
22
|
+
React.createElement(Ink.Text, { color: "#ef4444", dimColor: true }, "n")),
|
|
23
|
+
")"))));
|
|
24
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import * as Ink from "ink";
|
|
4
|
+
import { App } from "./app/App.js";
|
|
5
|
+
import { command } from "./command.js";
|
|
6
|
+
const argv = await command();
|
|
7
|
+
const ink = {};
|
|
8
|
+
const app = Ink.render(React.createElement(App, { argv: argv, ink: ink }));
|
|
9
|
+
Object.assign(ink, app);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { invariant } from "../core/invariant.js";
|
|
2
|
+
import { safe_quote } from "../core/safe_quote.js";
|
|
3
|
+
export function write(message, branch_id) {
|
|
4
|
+
let result = message;
|
|
5
|
+
// escape double-quote for cli
|
|
6
|
+
result = safe_quote(result);
|
|
7
|
+
// remove any previous metadata lines
|
|
8
|
+
result = remove(result);
|
|
9
|
+
const line_list = [result, "", TEMPLATE.branch_id(branch_id)];
|
|
10
|
+
const new_message = line_list.join("\n");
|
|
11
|
+
return new_message;
|
|
12
|
+
}
|
|
13
|
+
export function read(message) {
|
|
14
|
+
const match = message.match(RE.branch_id);
|
|
15
|
+
if (!match?.groups) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const id = match.groups["id"];
|
|
19
|
+
invariant(id, "id must exist");
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
export function remove(message) {
|
|
23
|
+
let result = message;
|
|
24
|
+
// remove metadata
|
|
25
|
+
result = result.replace(new RegExp(RE.branch_id, "g"), "");
|
|
26
|
+
result = result.trimEnd();
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
const TEMPLATE = {
|
|
30
|
+
branch_id(id) {
|
|
31
|
+
return `git-stack-id: ${id}`;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const RE = {
|
|
35
|
+
all_double_quote: /"/g,
|
|
36
|
+
branch_id: new RegExp(TEMPLATE.branch_id("(?<id>[a-z0-9-+]+)"), "i"),
|
|
37
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { invariant } from "./invariant.js";
|
|
2
|
+
import { safe_quote } from "./safe_quote.js";
|
|
3
|
+
export function write(message, branch_id) {
|
|
4
|
+
let result = message;
|
|
5
|
+
// escape double-quote for cli
|
|
6
|
+
result = safe_quote(result);
|
|
7
|
+
// remove any previous metadata lines
|
|
8
|
+
result = remove(result);
|
|
9
|
+
const line_list = [result, "", TEMPLATE.branch_id(branch_id)];
|
|
10
|
+
const new_message = line_list.join("\n");
|
|
11
|
+
return new_message;
|
|
12
|
+
}
|
|
13
|
+
export function read(message) {
|
|
14
|
+
const match = message.match(RE.branch_id);
|
|
15
|
+
if (!match?.groups) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const id = match.groups["id"];
|
|
19
|
+
invariant(id, "id must exist");
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
export function remove(message) {
|
|
23
|
+
let result = message;
|
|
24
|
+
// remove metadata
|
|
25
|
+
result = result.replace(new RegExp(RE.branch_id, "g"), "");
|
|
26
|
+
result = result.trimEnd();
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
const TEMPLATE = {
|
|
30
|
+
stack_table(rows) {
|
|
31
|
+
return `"#### git stack\n${rows}"`;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const RE = {
|
|
35
|
+
all_double_quote: /"/g,
|
|
36
|
+
// https://regex101.com/r/kqB9Ft/1
|
|
37
|
+
stack_table: new RegExp(TEMPLATE.branch_id("(?<id>[a-z0-9-+]+)"), "i"),
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { invariant } from "./invariant.js";
|
|
2
|
+
import { safe_quote } from "./safe_quote.js";
|
|
3
|
+
export function write(message, branch_id) {
|
|
4
|
+
let result = message;
|
|
5
|
+
// escape double-quote for cli
|
|
6
|
+
result = safe_quote(result);
|
|
7
|
+
// remove any previous metadata lines
|
|
8
|
+
result = remove(result);
|
|
9
|
+
const line_list = [result, "", TEMPLATE.branch_id(branch_id)];
|
|
10
|
+
const new_message = line_list.join("\n");
|
|
11
|
+
return new_message;
|
|
12
|
+
}
|
|
13
|
+
export function read(message) {
|
|
14
|
+
const match = message.match(RE.branch_id);
|
|
15
|
+
if (!match?.groups) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const id = match.groups["id"];
|
|
19
|
+
invariant(id, "id must exist");
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
export function remove(message) {
|
|
23
|
+
let result = message;
|
|
24
|
+
// remove metadata
|
|
25
|
+
result = result.replace(new RegExp(RE.branch_id, "g"), "");
|
|
26
|
+
result = result.trimEnd();
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
const TEMPLATE = {
|
|
30
|
+
stack_table(rows) {
|
|
31
|
+
return `"#### git stack\n${rows}"`;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const RE = {
|
|
35
|
+
all_double_quote: /"/g,
|
|
36
|
+
// https://regex101.com/r/kqB9Ft/1
|
|
37
|
+
stack_table: new RegExp(TEMPLATE.branch_id("(?<id>[a-z0-9-+]+)"), "i"),
|
|
38
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createStore, useStore } from "zustand";
|
|
2
|
+
import { immer } from "zustand/middleware/immer";
|
|
3
|
+
function ZustandStore() {
|
|
4
|
+
const BaseStore = createStore()(immer((set, get) => ({
|
|
5
|
+
actions: {
|
|
6
|
+
set(setter) {
|
|
7
|
+
set((state) => {
|
|
8
|
+
setter(state);
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})));
|
|
13
|
+
function useState(selector) {
|
|
14
|
+
return useStore(BaseStore, selector);
|
|
15
|
+
}
|
|
16
|
+
function useActions() {
|
|
17
|
+
return useState((state) => state.actions);
|
|
18
|
+
}
|
|
19
|
+
const getState = BaseStore.getState;
|
|
20
|
+
const setState = BaseStore.setState;
|
|
21
|
+
const subscribe = BaseStore.subscribe;
|
|
22
|
+
return { useActions, useState, getState, setState, subscribe };
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as child from "node:child_process";
|
|
2
|
+
export async function cli(command, unsafe_options) {
|
|
3
|
+
const options = Object.assign({}, unsafe_options);
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const childProcess = child.spawn("sh", ["-c", command], options);
|
|
6
|
+
let stdout = "";
|
|
7
|
+
let stderr = "";
|
|
8
|
+
let output = "";
|
|
9
|
+
childProcess.stdout?.on("data", (data) => {
|
|
10
|
+
stdout += data.toString();
|
|
11
|
+
output += data.toString();
|
|
12
|
+
});
|
|
13
|
+
childProcess.stderr?.on("data", (data) => {
|
|
14
|
+
stderr += data.toString();
|
|
15
|
+
output += data.toString();
|
|
16
|
+
});
|
|
17
|
+
childProcess.on("close", (code) => {
|
|
18
|
+
if (!options.ignoreExitCode && code !== 0) {
|
|
19
|
+
reject(new Error(`[${command}] (${code})`));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const result = {
|
|
23
|
+
code: code || 0,
|
|
24
|
+
stdout: stdout.trimEnd(),
|
|
25
|
+
stderr: stderr.trimEnd(),
|
|
26
|
+
output: output.trimEnd(),
|
|
27
|
+
};
|
|
28
|
+
resolve(result);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
childProcess.on("error", (err) => {
|
|
32
|
+
reject(err);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
cli.sync = function cli_sync(command, unsafe_options) {
|
|
37
|
+
const options = Object.assign({}, unsafe_options);
|
|
38
|
+
const spawn_return = child.spawnSync("sh", ["-c", command], options);
|
|
39
|
+
const stdout = String(spawn_return.stdout);
|
|
40
|
+
const stderr = String(spawn_return.stderr);
|
|
41
|
+
const output = String(spawn_return.output);
|
|
42
|
+
const code = spawn_return.status || 0;
|
|
43
|
+
return { code, stdout, stderr, output };
|
|
44
|
+
};
|
|
@@ -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
|
+
}
|