git-stack-cli 1.0.2 → 1.0.4
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/cjs/index.cjs +3 -3
- package/package.json +15 -8
- package/rollup.config.mjs +46 -0
- package/scripts/.eslintrc.cjs +61 -0
- package/scripts/build-standalone.ts +73 -0
- package/scripts/core/create_asset.ts +21 -0
- package/scripts/core/file.ts +36 -0
- package/scripts/core/spawn.ts +62 -0
- package/scripts/npm-prepublishOnly.ts +8 -0
- package/scripts/release-brew.ts +69 -0
- package/scripts/release-github.ts +34 -0
- package/scripts/release-npm.ts +109 -0
- package/scripts/tsconfig.json +35 -0
- package/src/__fixtures__/metadata.ts +666 -0
- package/src/app/App.tsx +65 -0
- package/src/app/AutoUpdate.tsx +229 -0
- package/src/app/Await.tsx +82 -0
- package/src/app/Brackets.tsx +22 -0
- package/src/app/Command.tsx +19 -0
- package/src/app/Debug.tsx +52 -0
- package/src/app/DependencyCheck.tsx +155 -0
- package/src/app/Exit.tsx +25 -0
- package/src/app/FormatText.tsx +26 -0
- package/src/app/GatherMetadata.tsx +145 -0
- package/src/app/GithubApiError.tsx +78 -0
- package/src/app/LocalCommitStatus.tsx +70 -0
- package/src/app/LocalMergeRebase.tsx +230 -0
- package/src/app/LogTimestamp.tsx +12 -0
- package/src/app/Main.tsx +52 -0
- package/src/app/ManualRebase.tsx +308 -0
- package/src/app/MultiSelect.tsx +246 -0
- package/src/app/Output.tsx +37 -0
- package/src/app/Parens.tsx +21 -0
- package/src/app/PostRebaseStatus.tsx +33 -0
- package/src/app/PreLocalMergeRebase.tsx +31 -0
- package/src/app/PreSelectCommitRanges.tsx +31 -0
- package/src/app/Providers.tsx +11 -0
- package/src/app/RebaseCheck.tsx +96 -0
- package/src/app/SelectCommitRanges.tsx +372 -0
- package/src/app/Status.tsx +82 -0
- package/src/app/StatusTable.tsx +155 -0
- package/src/app/Store.tsx +252 -0
- package/src/app/Table.tsx +137 -0
- package/src/app/TextInput.tsx +88 -0
- package/src/app/Url.tsx +19 -0
- package/src/app/Waterfall.tsx +37 -0
- package/src/app/YesNoPrompt.tsx +73 -0
- package/src/command.ts +78 -0
- package/src/core/CommitMetadata.ts +212 -0
- package/src/core/Metadata.test.ts +41 -0
- package/src/core/Metadata.ts +51 -0
- package/src/core/StackSummaryTable.test.ts +157 -0
- package/src/core/StackSummaryTable.ts +127 -0
- package/src/core/Timer.ts +44 -0
- package/src/core/assertNever.ts +4 -0
- package/src/core/cache.ts +49 -0
- package/src/core/capitalize.ts +5 -0
- package/src/core/chalk.ts +103 -0
- package/src/core/clamp.ts +6 -0
- package/src/core/cli.ts +161 -0
- package/src/core/colors.ts +23 -0
- package/src/core/date.ts +25 -0
- package/src/core/fetch_json.ts +26 -0
- package/src/core/github.tsx +215 -0
- package/src/core/invariant.ts +5 -0
- package/src/core/is_command_available.ts +21 -0
- package/src/core/is_finite_value.ts +3 -0
- package/src/core/json.ts +32 -0
- package/src/core/match_group.ts +10 -0
- package/src/core/read_json.ts +12 -0
- package/src/core/safe_quote.ts +10 -0
- package/src/core/semver_compare.ts +27 -0
- package/src/core/short_id.ts +87 -0
- package/src/core/sleep.ts +3 -0
- package/src/core/wrap_index.ts +11 -0
- package/src/index.tsx +22 -0
- package/src/types/global.d.ts +7 -0
- package/tsconfig.json +53 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
import { createStore, useStore } from "zustand";
|
|
5
|
+
import { immer } from "zustand/middleware/immer";
|
|
6
|
+
|
|
7
|
+
import { Exit } from "~/app/Exit";
|
|
8
|
+
import { LogTimestamp } from "~/app/LogTimestamp";
|
|
9
|
+
import { colors } from "~/core/colors";
|
|
10
|
+
|
|
11
|
+
import type { Instance as InkInstance } from "ink-cjs";
|
|
12
|
+
import type { Argv } from "~/command";
|
|
13
|
+
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
14
|
+
import type { PullRequest } from "~/core/github";
|
|
15
|
+
|
|
16
|
+
type Setter = (state: State) => void;
|
|
17
|
+
|
|
18
|
+
type CommitMap = Parameters<typeof CommitMetadata.range>[0];
|
|
19
|
+
|
|
20
|
+
type MutateOutputArgs = {
|
|
21
|
+
node: React.ReactNode;
|
|
22
|
+
id?: string;
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type State = {
|
|
27
|
+
argv: null | Argv;
|
|
28
|
+
ink: null | InkInstance;
|
|
29
|
+
|
|
30
|
+
cwd: null | string;
|
|
31
|
+
username: null | string;
|
|
32
|
+
repo_path: null | string;
|
|
33
|
+
repo_root: null | string;
|
|
34
|
+
master_branch: string;
|
|
35
|
+
head: null | string;
|
|
36
|
+
merge_base: null | string;
|
|
37
|
+
branch_name: null | string;
|
|
38
|
+
commit_range: null | CommitMetadata.CommitRange;
|
|
39
|
+
commit_map: null | CommitMap;
|
|
40
|
+
|
|
41
|
+
step:
|
|
42
|
+
| "github-api-error"
|
|
43
|
+
| "loading"
|
|
44
|
+
| "status"
|
|
45
|
+
| "pre-local-merge-rebase"
|
|
46
|
+
| "local-merge-rebase"
|
|
47
|
+
| "pre-select-commit-ranges"
|
|
48
|
+
| "select-commit-ranges"
|
|
49
|
+
| "manual-rebase"
|
|
50
|
+
| "manual-rebase-no-sync"
|
|
51
|
+
| "post-rebase-status";
|
|
52
|
+
|
|
53
|
+
output: Array<React.ReactNode>;
|
|
54
|
+
pending_output: Record<string, Array<React.ReactNode>>;
|
|
55
|
+
|
|
56
|
+
pr: { [branch: string]: PullRequest };
|
|
57
|
+
|
|
58
|
+
actions: {
|
|
59
|
+
exit(code: number, clear?: boolean): void;
|
|
60
|
+
clear(): void;
|
|
61
|
+
unmount(): void;
|
|
62
|
+
newline(): void;
|
|
63
|
+
json(value: object): void;
|
|
64
|
+
error(message: string): void;
|
|
65
|
+
output(node: React.ReactNode): void;
|
|
66
|
+
debug(node: React.ReactNode, id?: string): void;
|
|
67
|
+
|
|
68
|
+
isDebug(): boolean;
|
|
69
|
+
|
|
70
|
+
reset_pr(): void;
|
|
71
|
+
|
|
72
|
+
set(setter: Setter): void;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
mutate: {
|
|
76
|
+
output(state: State, args: MutateOutputArgs): void;
|
|
77
|
+
pending_output(state: State, args: MutateOutputArgs): void;
|
|
78
|
+
end_pending_output(state: State, id: string): void;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
select: {
|
|
82
|
+
debug(state: State): boolean;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const BaseStore = createStore<State>()(
|
|
87
|
+
immer((set, get) => ({
|
|
88
|
+
argv: null,
|
|
89
|
+
ink: null,
|
|
90
|
+
|
|
91
|
+
cwd: null,
|
|
92
|
+
username: null,
|
|
93
|
+
repo_path: null,
|
|
94
|
+
repo_root: null,
|
|
95
|
+
master_branch: "master",
|
|
96
|
+
head: null,
|
|
97
|
+
merge_base: null,
|
|
98
|
+
branch_name: null,
|
|
99
|
+
commit_range: null,
|
|
100
|
+
commit_map: null,
|
|
101
|
+
|
|
102
|
+
step: "loading",
|
|
103
|
+
|
|
104
|
+
output: [],
|
|
105
|
+
pending_output: {},
|
|
106
|
+
|
|
107
|
+
pr: {},
|
|
108
|
+
|
|
109
|
+
actions: {
|
|
110
|
+
exit(code, clear = true) {
|
|
111
|
+
set((state) => {
|
|
112
|
+
const node = <Exit clear={clear} code={code} />;
|
|
113
|
+
state.mutate.output(state, { node });
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
clear() {
|
|
118
|
+
get().ink?.clear();
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
unmount() {
|
|
122
|
+
get().ink?.unmount();
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
newline() {
|
|
126
|
+
set((state) => {
|
|
127
|
+
const node = "";
|
|
128
|
+
state.mutate.output(state, { node });
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
json(value) {
|
|
133
|
+
set((state) => {
|
|
134
|
+
const node = JSON.stringify(value, null, 2);
|
|
135
|
+
state.mutate.output(state, { node });
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
error(message) {
|
|
140
|
+
set((state) => {
|
|
141
|
+
const node = <Ink.Text color={colors.red}>{message}</Ink.Text>;
|
|
142
|
+
state.mutate.output(state, { node });
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
output(node) {
|
|
147
|
+
set((state) => {
|
|
148
|
+
state.mutate.output(state, { node });
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
debug(node, id) {
|
|
153
|
+
if (get().actions.isDebug()) {
|
|
154
|
+
const debug = true;
|
|
155
|
+
|
|
156
|
+
set((state) => {
|
|
157
|
+
if (id) {
|
|
158
|
+
state.mutate.pending_output(state, { id, node, debug });
|
|
159
|
+
} else {
|
|
160
|
+
state.mutate.output(state, { node, debug });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
isDebug() {
|
|
167
|
+
const state = get();
|
|
168
|
+
return state.select.debug(state);
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
reset_pr() {
|
|
172
|
+
set((state) => {
|
|
173
|
+
state.pr = {};
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
set(setter) {
|
|
178
|
+
set((state) => {
|
|
179
|
+
setter(state);
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
mutate: {
|
|
185
|
+
output(state, args) {
|
|
186
|
+
const renderOutput = renderOutputArgs(args);
|
|
187
|
+
state.output.push(renderOutput);
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
pending_output(state, args) {
|
|
191
|
+
const { id } = args;
|
|
192
|
+
|
|
193
|
+
if (!id) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!state.pending_output[id]) {
|
|
198
|
+
state.pending_output[id] = [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const renderOutput = renderOutputArgs(args);
|
|
202
|
+
state.pending_output[id].push(renderOutput);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
end_pending_output(state, id) {
|
|
206
|
+
delete state.pending_output[id];
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
select: {
|
|
211
|
+
debug(state) {
|
|
212
|
+
return state.argv?.verbose || false;
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}))
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
function renderOutputArgs(args: MutateOutputArgs) {
|
|
219
|
+
let output = args.node;
|
|
220
|
+
|
|
221
|
+
switch (typeof args.node) {
|
|
222
|
+
case "boolean":
|
|
223
|
+
case "number":
|
|
224
|
+
case "string":
|
|
225
|
+
output = <Ink.Text dimColor={args.debug}>{String(args.node)}</Ink.Text>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (args.debug) {
|
|
229
|
+
return (
|
|
230
|
+
<React.Fragment>
|
|
231
|
+
<LogTimestamp />
|
|
232
|
+
{output}
|
|
233
|
+
</React.Fragment>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return output;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function useState<R>(selector: (state: State) => R): R {
|
|
241
|
+
return useStore(BaseStore, selector);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function useActions() {
|
|
245
|
+
return useState((state) => state.actions);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const getState = BaseStore.getState;
|
|
249
|
+
const setState = BaseStore.setState;
|
|
250
|
+
const subscribe = BaseStore.subscribe;
|
|
251
|
+
|
|
252
|
+
export const Store = { useActions, useState, getState, setState, subscribe };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { is_finite_value } from "~/core/is_finite_value";
|
|
6
|
+
|
|
7
|
+
type Props<T extends BaseRow> = {
|
|
8
|
+
data: Array<T>;
|
|
9
|
+
columns: ColumnComponentMap<T>;
|
|
10
|
+
columnGap?: number;
|
|
11
|
+
fillColumn?: Column<T>;
|
|
12
|
+
maxWidth?: Partial<{ [key in Column<T>]: (maxWidth: number) => number }>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function Table<T extends BaseRow>(props: Props<T>) {
|
|
16
|
+
if (!props.data.length) {
|
|
17
|
+
return (
|
|
18
|
+
<Container>
|
|
19
|
+
<Ink.Text dimColor>No data found.</Ink.Text>
|
|
20
|
+
</Container>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const RowColumnList = Object.keys(props.columns) as Array<Column<T>>;
|
|
25
|
+
|
|
26
|
+
// walk data and discover max width for each column
|
|
27
|
+
const max_col_width = {} as { [key in Column<T>]: number };
|
|
28
|
+
|
|
29
|
+
for (const col of RowColumnList) {
|
|
30
|
+
max_col_width[col] = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const row of props.data) {
|
|
34
|
+
for (const col of RowColumnList) {
|
|
35
|
+
const row_col = row[col];
|
|
36
|
+
max_col_width[col] = Math.max(String(row_col).length, max_col_width[col]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const col of RowColumnList) {
|
|
41
|
+
const maxWidth = props.maxWidth?.[col];
|
|
42
|
+
if (maxWidth) {
|
|
43
|
+
max_col_width[col] = maxWidth(max_col_width[col]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { stdout } = Ink.useStdout();
|
|
48
|
+
const available_width = stdout.columns;
|
|
49
|
+
const columnGap = is_finite_value(props.columnGap) ? props.columnGap : 2;
|
|
50
|
+
|
|
51
|
+
// single character breathing room to prevent url including next line via overflow
|
|
52
|
+
const breathing_room = 1;
|
|
53
|
+
|
|
54
|
+
if (props.fillColumn) {
|
|
55
|
+
let remaining_space = available_width;
|
|
56
|
+
|
|
57
|
+
for (const col of RowColumnList) {
|
|
58
|
+
// skip fill column from calculation
|
|
59
|
+
if (props.fillColumn === col) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
remaining_space -= max_col_width[col];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// gap * col count
|
|
67
|
+
remaining_space -= columnGap * (RowColumnList.length - 1);
|
|
68
|
+
|
|
69
|
+
// remove some extra space
|
|
70
|
+
remaining_space -= breathing_room;
|
|
71
|
+
|
|
72
|
+
if (props.fillColumn) {
|
|
73
|
+
max_col_width[props.fillColumn] = Math.min(
|
|
74
|
+
max_col_width[props.fillColumn],
|
|
75
|
+
remaining_space
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// console.debug({ available_width, max_col_width });
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Container>
|
|
84
|
+
{props.data.map((row, i) => {
|
|
85
|
+
return (
|
|
86
|
+
<Ink.Box
|
|
87
|
+
key={i}
|
|
88
|
+
// borderStyle="round"
|
|
89
|
+
flexDirection="row"
|
|
90
|
+
columnGap={columnGap}
|
|
91
|
+
width={available_width}
|
|
92
|
+
>
|
|
93
|
+
{RowColumnList.map((column) => {
|
|
94
|
+
const ColumnComponent = props.columns[
|
|
95
|
+
column
|
|
96
|
+
] as ColumnComponent<T>;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Ink.Box key={String(column)} width={max_col_width[column]}>
|
|
100
|
+
<ColumnComponent row={row} column={column} />
|
|
101
|
+
</Ink.Box>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</Ink.Box>
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
</Container>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Container(props: { children: React.ReactNode }) {
|
|
112
|
+
return (
|
|
113
|
+
<Ink.Box flexDirection="column">
|
|
114
|
+
<Ink.Box height={1} />
|
|
115
|
+
{props.children}
|
|
116
|
+
<Ink.Box height={1} />
|
|
117
|
+
</Ink.Box>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type BaseRow = Record<string, string | number>;
|
|
122
|
+
|
|
123
|
+
type Column<T extends BaseRow> = keyof T;
|
|
124
|
+
|
|
125
|
+
type ColumnComponent<T extends BaseRow> = (
|
|
126
|
+
props: TableColumnProps<T>
|
|
127
|
+
) => React.ReactNode;
|
|
128
|
+
|
|
129
|
+
type ColumnComponentMap<T extends BaseRow> = Record<
|
|
130
|
+
Column<T>,
|
|
131
|
+
ColumnComponent<T>
|
|
132
|
+
>;
|
|
133
|
+
|
|
134
|
+
export type TableColumnProps<T extends BaseRow> = {
|
|
135
|
+
row: T;
|
|
136
|
+
column: keyof T;
|
|
137
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { colors } from "~/core/colors";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
multiline?: boolean;
|
|
9
|
+
value?: string;
|
|
10
|
+
onChange?: (value: string) => void;
|
|
11
|
+
onSubmit?: (value: string) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function TextInput(props: Props) {
|
|
15
|
+
const [value, set_value] = React.useState(get_value(props));
|
|
16
|
+
|
|
17
|
+
React.useEffect(
|
|
18
|
+
function sync_value_prop() {
|
|
19
|
+
set_value(get_value(props));
|
|
20
|
+
},
|
|
21
|
+
[props.value]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const [caret_visible, set_caret_visible] = React.useState(false);
|
|
25
|
+
|
|
26
|
+
React.useEffect(function blink_caret() {
|
|
27
|
+
const interval_ms = 500;
|
|
28
|
+
|
|
29
|
+
let timeoutId = setTimeout(tick, interval_ms);
|
|
30
|
+
|
|
31
|
+
function tick() {
|
|
32
|
+
set_caret_visible((visible) => !visible);
|
|
33
|
+
timeoutId = setTimeout(tick, interval_ms);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return function cleanup() {
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
Ink.useInput((input, key) => {
|
|
42
|
+
let next_value = value;
|
|
43
|
+
|
|
44
|
+
// console.debug("[useInput]", { input, key });
|
|
45
|
+
|
|
46
|
+
if (key.backspace || key.delete) {
|
|
47
|
+
next_value = value.slice(0, -1);
|
|
48
|
+
} else if (key.return) {
|
|
49
|
+
props.onSubmit?.(next_value);
|
|
50
|
+
} else {
|
|
51
|
+
switch (input) {
|
|
52
|
+
case "\r":
|
|
53
|
+
if (props.multiline) {
|
|
54
|
+
next_value = `${value}\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
default:
|
|
60
|
+
next_value = `${value}${input}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
set_value(next_value);
|
|
65
|
+
props.onChange?.(next_value);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// console.debug("[TextInput]", { value });
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Ink.Box
|
|
72
|
+
borderStyle="single"
|
|
73
|
+
minHeight={1}
|
|
74
|
+
borderColor={colors.yellow}
|
|
75
|
+
borderDimColor
|
|
76
|
+
>
|
|
77
|
+
<Ink.Text>{value || ""}</Ink.Text>
|
|
78
|
+
|
|
79
|
+
<Ink.Text color={colors.yellow} dimColor inverse={caret_visible}>
|
|
80
|
+
{" "}
|
|
81
|
+
</Ink.Text>
|
|
82
|
+
</Ink.Box>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function get_value(props: Props) {
|
|
87
|
+
return props.value || "";
|
|
88
|
+
}
|
package/src/app/Url.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { colors } from "~/core/colors";
|
|
6
|
+
|
|
7
|
+
type Props = InkTextProps & {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type InkTextProps = React.ComponentProps<typeof Ink.Text>;
|
|
12
|
+
|
|
13
|
+
export function Url(props: Props) {
|
|
14
|
+
return (
|
|
15
|
+
<Ink.Text bold color={colors.blue} {...props}>
|
|
16
|
+
{props.children}
|
|
17
|
+
</Ink.Text>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { Await } from "~/app/Await";
|
|
6
|
+
import { Store } from "~/app/Store";
|
|
7
|
+
import { sleep } from "~/core/sleep";
|
|
8
|
+
|
|
9
|
+
export function Waterfall() {
|
|
10
|
+
return (
|
|
11
|
+
<Await
|
|
12
|
+
fallback={<Ink.Text>Loading...</Ink.Text>}
|
|
13
|
+
function={async () => {
|
|
14
|
+
await sleep(3 * 1000);
|
|
15
|
+
Store.getState().actions.output(<Ink.Text>apple</Ink.Text>);
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
<Await
|
|
19
|
+
fallback={<Ink.Text>Loading...</Ink.Text>}
|
|
20
|
+
function={async () => {
|
|
21
|
+
await sleep(3 * 1000);
|
|
22
|
+
Store.getState().actions.output(<Ink.Text>banana</Ink.Text>);
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Await
|
|
26
|
+
fallback={<Ink.Text>Loading...</Ink.Text>}
|
|
27
|
+
function={async () => {
|
|
28
|
+
await sleep(3 * 1000);
|
|
29
|
+
Store.getState().actions.output(<Ink.Text>orange</Ink.Text>);
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<Ink.Text>Waterfall</Ink.Text>
|
|
33
|
+
</Await>
|
|
34
|
+
</Await>
|
|
35
|
+
</Await>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { Parens } from "~/app/Parens";
|
|
6
|
+
import { colors } from "~/core/colors";
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
message: React.ReactNode;
|
|
10
|
+
onYes(): void;
|
|
11
|
+
onNo(): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function YesNoPrompt(props: Props) {
|
|
15
|
+
const [answer, set_answer] = React.useState("");
|
|
16
|
+
|
|
17
|
+
Ink.useInput((input) => {
|
|
18
|
+
const inputLower = input.toLowerCase();
|
|
19
|
+
|
|
20
|
+
set_answer(inputLower);
|
|
21
|
+
|
|
22
|
+
switch (inputLower) {
|
|
23
|
+
case "n":
|
|
24
|
+
return props.onNo();
|
|
25
|
+
|
|
26
|
+
case "y":
|
|
27
|
+
return props.onYes();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// prettier-ignore
|
|
32
|
+
const y = <Ink.Text bold color={colors.green}>Y</Ink.Text>;
|
|
33
|
+
const n = <Ink.Text color={colors.red}>n</Ink.Text>;
|
|
34
|
+
|
|
35
|
+
let choices;
|
|
36
|
+
|
|
37
|
+
switch (answer) {
|
|
38
|
+
case "y":
|
|
39
|
+
choices = y;
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
case "n":
|
|
43
|
+
choices = n;
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
choices = (
|
|
48
|
+
<React.Fragment>
|
|
49
|
+
{y}
|
|
50
|
+
<Ink.Text>/</Ink.Text>
|
|
51
|
+
{n}
|
|
52
|
+
</React.Fragment>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Ink.Box flexDirection="column">
|
|
58
|
+
<Ink.Box>
|
|
59
|
+
{typeof props.message === "object" ? (
|
|
60
|
+
props.message
|
|
61
|
+
) : (
|
|
62
|
+
<Ink.Text color={colors.yellow}>{props.message}</Ink.Text>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
<Ink.Text> </Ink.Text>
|
|
66
|
+
|
|
67
|
+
<Parens>
|
|
68
|
+
<Ink.Text color={colors.gray}>{choices}</Ink.Text>
|
|
69
|
+
</Parens>
|
|
70
|
+
</Ink.Box>
|
|
71
|
+
</Ink.Box>
|
|
72
|
+
);
|
|
73
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import yargs from "yargs";
|
|
2
|
+
import { hideBin } from "yargs/helpers";
|
|
3
|
+
|
|
4
|
+
export type Argv = Awaited<ReturnType<typeof command>>;
|
|
5
|
+
|
|
6
|
+
export async function command() {
|
|
7
|
+
// https://yargs.js.org/docs/#api-reference-optionkey-opt
|
|
8
|
+
return (
|
|
9
|
+
yargs(hideBin(process.argv))
|
|
10
|
+
.usage("Usage: git stack [options]")
|
|
11
|
+
|
|
12
|
+
.option("force", {
|
|
13
|
+
type: "boolean",
|
|
14
|
+
alias: ["f"],
|
|
15
|
+
default: false,
|
|
16
|
+
description: "Force sync even if no changes are detected",
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
.option("check", {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
alias: ["c"],
|
|
22
|
+
default: false,
|
|
23
|
+
description: "Print status table without syncing",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
.option("verify", {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
default: true,
|
|
29
|
+
description: "Skip git hooks such as pre-commit and pre-push",
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
.option("verbose", {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
alias: ["v"],
|
|
35
|
+
default: false,
|
|
36
|
+
description: "Print more detailed logs for debugging internals",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
.option("update", {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
alias: ["u", "upgrade"],
|
|
42
|
+
default: false,
|
|
43
|
+
description: "Check and install the latest version",
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
.option("branch", {
|
|
47
|
+
type: "string",
|
|
48
|
+
alias: ["b"],
|
|
49
|
+
description: `Set the master branch name, defaults to "master" (or "main" if "master" is not found)`,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
.option("write-state-json", {
|
|
53
|
+
hidden: true,
|
|
54
|
+
type: "boolean",
|
|
55
|
+
default: false,
|
|
56
|
+
description: "Write state to local json file for debugging",
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
.option("mock-metadata", {
|
|
60
|
+
hidden: true,
|
|
61
|
+
type: "boolean",
|
|
62
|
+
default: false,
|
|
63
|
+
description: "Mock local store metadata for testing",
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// do not wrap to 80 columns (yargs default)
|
|
67
|
+
// .wrap(yargs().terminalWidth()) will fill terminal (maximuize)
|
|
68
|
+
.wrap(null)
|
|
69
|
+
// disallow unknown options
|
|
70
|
+
.strict()
|
|
71
|
+
.version(process.env.CLI_VERSION || "unknown")
|
|
72
|
+
.showHidden(
|
|
73
|
+
"show-hidden",
|
|
74
|
+
"Show hidden options via `git stack help --show-hidden`"
|
|
75
|
+
)
|
|
76
|
+
.help("help", "Show usage via `git stack help`").argv
|
|
77
|
+
);
|
|
78
|
+
}
|