git-stack-cli 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +21 -6
- 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 +10 -8
- package/dist/app/TextInput.js +16 -2
- package/dist/app/Url.js +2 -2
- package/dist/app/YesNoPrompt.js +5 -4
- package/dist/command.js +12 -1
- package/dist/core/CommitMetadata.js +6 -4
- package/dist/core/Metadata copy.js +37 -0
- package/dist/core/Metadata.js +0 -1
- package/dist/core/StackSummaryTable.js +35 -0
- package/dist/core/StackTable.js +38 -0
- package/dist/core/SummaryTable.js +38 -0
- package/dist/core/chalk.js +83 -0
- package/dist/core/colors.js +15 -0
- package/dist/core/github.js +23 -7
- package/package.json +3 -2
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,16 +16,19 @@ 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();
|
|
27
28
|
const available_width = stdout.columns;
|
|
28
29
|
const columnGap = is_finite_value(props.columnGap) ? props.columnGap : 2;
|
|
29
|
-
|
|
30
|
+
// single character breathing room to prevent url including next line via overflow
|
|
31
|
+
const breathing_room = 1;
|
|
30
32
|
if (props.fillColumn) {
|
|
31
33
|
let remaining_space = available_width;
|
|
32
34
|
for (const col of RowColumnList) {
|
|
@@ -44,7 +46,7 @@ export function Table(props) {
|
|
|
44
46
|
max_col_width[props.fillColumn] = Math.min(max_col_width[props.fillColumn], remaining_space);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
// console.debug({ available_width,
|
|
49
|
+
// console.debug({ available_width, max_col_width });
|
|
48
50
|
return (React.createElement(Container, null, props.data.map((row, i) => {
|
|
49
51
|
return (React.createElement(Ink.Box, { key: i,
|
|
50
52
|
// borderStyle="round"
|
package/dist/app/TextInput.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
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() {
|
|
6
7
|
set_value(get_value(props));
|
|
7
8
|
}, [props.value]);
|
|
9
|
+
const [caret_visible, set_caret_visible] = React.useState(false);
|
|
10
|
+
React.useEffect(function blink_caret() {
|
|
11
|
+
const interval_ms = 500;
|
|
12
|
+
let timeoutId = setTimeout(tick, interval_ms);
|
|
13
|
+
function tick() {
|
|
14
|
+
set_caret_visible((visible) => !visible);
|
|
15
|
+
timeoutId = setTimeout(tick, interval_ms);
|
|
16
|
+
}
|
|
17
|
+
return function cleanup() {
|
|
18
|
+
clearTimeout(timeoutId);
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
8
21
|
Ink.useInput((input, key) => {
|
|
9
22
|
let next_value = value;
|
|
10
23
|
// console.debug("[useInput]", { input, key });
|
|
@@ -29,8 +42,9 @@ export function TextInput(props) {
|
|
|
29
42
|
props.onChange?.(next_value);
|
|
30
43
|
});
|
|
31
44
|
// console.debug("[TextInput]", { value });
|
|
32
|
-
return (React.createElement(Ink.Box, { borderStyle: "single", minHeight: 1, borderColor:
|
|
33
|
-
React.createElement(Ink.Text, null, value || "
|
|
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 }, " ")));
|
|
34
48
|
}
|
|
35
49
|
function get_value(props) {
|
|
36
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
|
@@ -7,30 +7,41 @@ export async function command() {
|
|
|
7
7
|
.option("force", {
|
|
8
8
|
type: "boolean",
|
|
9
9
|
alias: ["f"],
|
|
10
|
+
default: false,
|
|
10
11
|
description: "Force sync even if no changes are detected",
|
|
11
12
|
})
|
|
12
13
|
.option("check", {
|
|
13
14
|
type: "boolean",
|
|
14
15
|
alias: ["c"],
|
|
16
|
+
default: false,
|
|
15
17
|
description: "Print status table without syncing",
|
|
16
18
|
})
|
|
17
|
-
.option("
|
|
19
|
+
.option("verify", {
|
|
18
20
|
type: "boolean",
|
|
21
|
+
default: true,
|
|
19
22
|
description: "Disable the pre-push hook, bypassing it completely",
|
|
20
23
|
})
|
|
21
24
|
.option("verbose", {
|
|
22
25
|
type: "boolean",
|
|
23
26
|
alias: ["v"],
|
|
27
|
+
default: false,
|
|
24
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)`,
|
|
25
34
|
})
|
|
26
35
|
.option("write-state-json", {
|
|
27
36
|
hidden: true,
|
|
28
37
|
type: "boolean",
|
|
38
|
+
default: false,
|
|
29
39
|
description: "Write state to local json file for debugging",
|
|
30
40
|
})
|
|
31
41
|
.option("mock-metadata", {
|
|
32
42
|
hidden: true,
|
|
33
43
|
type: "boolean",
|
|
44
|
+
default: false,
|
|
34
45
|
description: "Mock local store metadata for testing",
|
|
35
46
|
})
|
|
36
47
|
// do not wrap to 80 columns (yargs default)
|
|
@@ -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`);
|
|
@@ -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
|
+
};
|
package/dist/core/Metadata.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
const stack_list = [];
|
|
7
|
+
for (const group of group_list) {
|
|
8
|
+
if (group.pr?.url) {
|
|
9
|
+
const selected = args.selected_group_id === group.id;
|
|
10
|
+
const icon = selected ? "👉" : "⏳";
|
|
11
|
+
stack_list.push(`- ${icon} ${group.pr.url}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const stack_table = TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
|
|
15
|
+
let result = args.body;
|
|
16
|
+
if (RE.stack_table.test(result)) {
|
|
17
|
+
// replace stack table
|
|
18
|
+
result = result.replace(new RegExp(RE.stack_table), stack_table);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// append stack table
|
|
22
|
+
result = `${result}\n\n${stack_table}`;
|
|
23
|
+
}
|
|
24
|
+
result = result.trimEnd();
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
const TEMPLATE = {
|
|
28
|
+
stack_table(rows) {
|
|
29
|
+
return `#### git stack${rows}`;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const RE = {
|
|
33
|
+
// https://regex101.com/r/kqB9Ft/1
|
|
34
|
+
stack_table: new RegExp(TEMPLATE.stack_table("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
|
|
35
|
+
};
|
|
@@ -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,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
|
+
};
|