git-stack-cli 1.0.1 → 1.0.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/README.md +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/package.json +13 -7
- package/rollup.config.mjs +46 -0
- package/scripts/.eslintrc.cjs +61 -0
- package/scripts/core/file.ts +32 -0
- package/scripts/core/spawn.ts +41 -0
- package/scripts/npm-prepublishOnly.ts +8 -0
- package/scripts/prepare-standalone.ts +59 -0
- package/scripts/release-brew.ts +105 -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
package/src/app/App.tsx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { AutoUpdate } from "~/app/AutoUpdate";
|
|
4
|
+
import { Debug } from "~/app/Debug";
|
|
5
|
+
import { DependencyCheck } from "~/app/DependencyCheck";
|
|
6
|
+
import { GatherMetadata } from "~/app/GatherMetadata";
|
|
7
|
+
import { GithubApiError } from "~/app/GithubApiError";
|
|
8
|
+
import { LocalCommitStatus } from "~/app/LocalCommitStatus";
|
|
9
|
+
import { Main } from "~/app/Main";
|
|
10
|
+
import { Output } from "~/app/Output";
|
|
11
|
+
import { Providers } from "~/app/Providers";
|
|
12
|
+
import { RebaseCheck } from "~/app/RebaseCheck";
|
|
13
|
+
import { Store } from "~/app/Store";
|
|
14
|
+
|
|
15
|
+
export function App() {
|
|
16
|
+
const actions = Store.useActions();
|
|
17
|
+
|
|
18
|
+
const ink = Store.useState((state) => state.ink);
|
|
19
|
+
const argv = Store.useState((state) => state.argv);
|
|
20
|
+
|
|
21
|
+
if (!ink || !argv) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// // debug component
|
|
26
|
+
// return (
|
|
27
|
+
// <React.Fragment>
|
|
28
|
+
// <Debug />
|
|
29
|
+
// <Output />
|
|
30
|
+
|
|
31
|
+
// <GithubApiError />
|
|
32
|
+
// </React.Fragment>
|
|
33
|
+
// );
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Providers>
|
|
37
|
+
<Debug />
|
|
38
|
+
<Output />
|
|
39
|
+
|
|
40
|
+
<AutoUpdate
|
|
41
|
+
name="git-stack-cli"
|
|
42
|
+
verbose={argv.verbose || argv.update}
|
|
43
|
+
timeoutMs={argv.update ? 30 * 1000 : 2 * 1000}
|
|
44
|
+
onOutput={actions.output}
|
|
45
|
+
onDone={() => {
|
|
46
|
+
if (argv.update) {
|
|
47
|
+
actions.exit(0);
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<RebaseCheck>
|
|
52
|
+
<DependencyCheck>
|
|
53
|
+
{!argv.verbose ? null : <GithubApiError />}
|
|
54
|
+
|
|
55
|
+
<GatherMetadata>
|
|
56
|
+
<LocalCommitStatus>
|
|
57
|
+
<Main />
|
|
58
|
+
</LocalCommitStatus>
|
|
59
|
+
</GatherMetadata>
|
|
60
|
+
</DependencyCheck>
|
|
61
|
+
</RebaseCheck>
|
|
62
|
+
</AutoUpdate>
|
|
63
|
+
</Providers>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import * as Ink from "ink-cjs";
|
|
7
|
+
|
|
8
|
+
import { Brackets } from "~/app/Brackets";
|
|
9
|
+
import { FormatText } from "~/app/FormatText";
|
|
10
|
+
import { YesNoPrompt } from "~/app/YesNoPrompt";
|
|
11
|
+
import { cli } from "~/core/cli";
|
|
12
|
+
import { colors } from "~/core/colors";
|
|
13
|
+
import { fetch_json } from "~/core/fetch_json";
|
|
14
|
+
import { is_finite_value } from "~/core/is_finite_value";
|
|
15
|
+
import { read_json } from "~/core/read_json";
|
|
16
|
+
import { semver_compare } from "~/core/semver_compare";
|
|
17
|
+
import { sleep } from "~/core/sleep";
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
name: string;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
verbose?: boolean;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
onError?: (error: Error) => void;
|
|
25
|
+
onOutput?: (output: React.ReactNode) => void;
|
|
26
|
+
onDone?: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type State = {
|
|
30
|
+
error: null | Error;
|
|
31
|
+
local_version: null | string;
|
|
32
|
+
latest_version: null | string;
|
|
33
|
+
status: "init" | "prompt" | "install" | "done" | "exit";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function reducer(state: State, patch: Partial<State>) {
|
|
37
|
+
return { ...state, ...patch };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function AutoUpdate(props: Props) {
|
|
41
|
+
const props_ref = React.useRef(props);
|
|
42
|
+
props_ref.current = props;
|
|
43
|
+
|
|
44
|
+
const [output, set_output] = React.useState<Array<React.ReactNode>>([]);
|
|
45
|
+
|
|
46
|
+
const [state, patch] = React.useReducer(reducer, {
|
|
47
|
+
error: null,
|
|
48
|
+
local_version: null,
|
|
49
|
+
latest_version: null,
|
|
50
|
+
status: "init",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function handle_output(node: React.ReactNode) {
|
|
54
|
+
if (typeof props.onOutput === "function") {
|
|
55
|
+
props.onOutput(node);
|
|
56
|
+
} else {
|
|
57
|
+
set_output((current) => {
|
|
58
|
+
return [...current, node];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
let status: State["status"] = "done";
|
|
65
|
+
let local_version: string | null = null;
|
|
66
|
+
let latest_version: string | null = null;
|
|
67
|
+
|
|
68
|
+
async function auto_update() {
|
|
69
|
+
if (props_ref.current.verbose) {
|
|
70
|
+
handle_output(
|
|
71
|
+
<Ink.Text key="init">Checking for latest version...</Ink.Text>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const timeout_ms = is_finite_value(props.timeoutMs)
|
|
76
|
+
? props.timeoutMs
|
|
77
|
+
: 2 * 1000;
|
|
78
|
+
|
|
79
|
+
const npm_json = await Promise.race([
|
|
80
|
+
fetch_json(`https://registry.npmjs.org/${props.name}`),
|
|
81
|
+
|
|
82
|
+
sleep(timeout_ms).then(() => {
|
|
83
|
+
throw new Error("Timeout");
|
|
84
|
+
}),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
latest_version = npm_json?.["dist-tags"]?.latest;
|
|
88
|
+
|
|
89
|
+
if (!latest_version) {
|
|
90
|
+
throw new Error("Unable to retrieve latest version from npm");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const script_dir = path.dirname(fs.realpathSync(process.argv[1]));
|
|
94
|
+
|
|
95
|
+
// dist/ts/index.js
|
|
96
|
+
const package_json_path = path.join(
|
|
97
|
+
script_dir,
|
|
98
|
+
"..",
|
|
99
|
+
"..",
|
|
100
|
+
"package.json"
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const package_json = read_json<{ version: string }>(package_json_path);
|
|
104
|
+
|
|
105
|
+
if (!package_json) {
|
|
106
|
+
// unable to find read package.json, skip auto update
|
|
107
|
+
throw new Error(`Unable to read package.json [${package_json_path}]`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
local_version = package_json.version;
|
|
111
|
+
|
|
112
|
+
if (props_ref.current.verbose) {
|
|
113
|
+
handle_output(
|
|
114
|
+
<FormatText
|
|
115
|
+
key="versions"
|
|
116
|
+
wrapper={<Ink.Text />}
|
|
117
|
+
message="Auto update found latest version {latest_version} and current local version {local_version}"
|
|
118
|
+
values={{
|
|
119
|
+
latest_version: <Brackets>{latest_version}</Brackets>,
|
|
120
|
+
local_version: <Brackets>{local_version}</Brackets>,
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const semver_result = semver_compare(latest_version, local_version);
|
|
127
|
+
|
|
128
|
+
if (semver_result === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (semver_result === -1) {
|
|
133
|
+
// latest version is less than or equal to local version, skip auto update
|
|
134
|
+
throw new Error(
|
|
135
|
+
`latest version < local_version, skipping auto update [${latest_version} < ${local_version}]`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// trigger yes no prompt
|
|
140
|
+
status = "prompt";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const onError = props_ref.current.onError || (() => {});
|
|
144
|
+
|
|
145
|
+
auto_update()
|
|
146
|
+
.then(() => {
|
|
147
|
+
patch({ status, local_version, latest_version });
|
|
148
|
+
})
|
|
149
|
+
.catch((error) => {
|
|
150
|
+
patch({ status, error, local_version, latest_version });
|
|
151
|
+
onError(error);
|
|
152
|
+
|
|
153
|
+
if (props_ref.current.verbose) {
|
|
154
|
+
handle_output(
|
|
155
|
+
<Ink.Text key="error" color={colors.red}>
|
|
156
|
+
{error?.message}
|
|
157
|
+
</Ink.Text>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
.finally(() => {
|
|
162
|
+
props.onDone?.();
|
|
163
|
+
});
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
const status = (function render_status() {
|
|
167
|
+
switch (state.status) {
|
|
168
|
+
case "init":
|
|
169
|
+
return null;
|
|
170
|
+
|
|
171
|
+
case "prompt":
|
|
172
|
+
return (
|
|
173
|
+
<YesNoPrompt
|
|
174
|
+
message={
|
|
175
|
+
<Ink.Text color={colors.yellow}>
|
|
176
|
+
New version available, would you like to update?
|
|
177
|
+
</Ink.Text>
|
|
178
|
+
}
|
|
179
|
+
onYes={async () => {
|
|
180
|
+
handle_output(
|
|
181
|
+
<FormatText
|
|
182
|
+
key="install"
|
|
183
|
+
wrapper={<Ink.Text />}
|
|
184
|
+
message="Installing {name}@{version}..."
|
|
185
|
+
values={{
|
|
186
|
+
name: (
|
|
187
|
+
<Ink.Text color={colors.yellow}>{props.name}</Ink.Text>
|
|
188
|
+
),
|
|
189
|
+
version: (
|
|
190
|
+
<Ink.Text color={colors.blue}>
|
|
191
|
+
{state.latest_version}
|
|
192
|
+
</Ink.Text>
|
|
193
|
+
),
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
patch({ status: "install" });
|
|
199
|
+
|
|
200
|
+
await cli(`npm install -g ${props.name}@latest`);
|
|
201
|
+
|
|
202
|
+
patch({ status: "exit" });
|
|
203
|
+
|
|
204
|
+
handle_output(<Ink.Text key="done">Auto update done.</Ink.Text>);
|
|
205
|
+
}}
|
|
206
|
+
onNo={() => {
|
|
207
|
+
patch({ status: "done" });
|
|
208
|
+
}}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
case "install":
|
|
213
|
+
return null;
|
|
214
|
+
|
|
215
|
+
case "exit":
|
|
216
|
+
return null;
|
|
217
|
+
|
|
218
|
+
case "done":
|
|
219
|
+
return props.children;
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<React.Fragment>
|
|
225
|
+
{output}
|
|
226
|
+
{status}
|
|
227
|
+
</React.Fragment>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cache } from "~/core/cache";
|
|
4
|
+
import { invariant } from "~/core/invariant";
|
|
5
|
+
|
|
6
|
+
type Cache = ReturnType<typeof cache>;
|
|
7
|
+
|
|
8
|
+
type BaseProps = {
|
|
9
|
+
function: Parameters<typeof cache>[0];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type WithChildrenProps = BaseProps & {
|
|
13
|
+
fallback: React.SuspenseProps["fallback"];
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
delayFallbackMs?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type WithoutChildrenProps = BaseProps;
|
|
19
|
+
|
|
20
|
+
type Props = WithChildrenProps | WithoutChildrenProps;
|
|
21
|
+
|
|
22
|
+
export function Await(props: Props) {
|
|
23
|
+
const [display_fallback, set_display_fallback] = React.useState(false);
|
|
24
|
+
|
|
25
|
+
// const id = React.useId();
|
|
26
|
+
const cacheRef = React.useRef<null | Cache>(null);
|
|
27
|
+
|
|
28
|
+
if (!cacheRef.current) {
|
|
29
|
+
// console.debug("setting cacheRef.current", { id });
|
|
30
|
+
cacheRef.current = cache(props.function);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let delayFallbackMs: number;
|
|
34
|
+
if ("delayFallbackMs" in props && typeof props.delayFallbackMs === "number") {
|
|
35
|
+
delayFallbackMs = props.delayFallbackMs;
|
|
36
|
+
} else {
|
|
37
|
+
delayFallbackMs = 1000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
const cache = cacheRef.current;
|
|
42
|
+
|
|
43
|
+
if (!cache) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
48
|
+
|
|
49
|
+
timeoutId = setTimeout(() => {
|
|
50
|
+
// only display fallback if we are still pending
|
|
51
|
+
if (cache.check() === "pending") {
|
|
52
|
+
set_display_fallback(true);
|
|
53
|
+
}
|
|
54
|
+
}, delayFallbackMs);
|
|
55
|
+
|
|
56
|
+
return function cleanup() {
|
|
57
|
+
clearTimeout(timeoutId);
|
|
58
|
+
};
|
|
59
|
+
}, [delayFallbackMs]);
|
|
60
|
+
|
|
61
|
+
invariant(cacheRef.current, "cache must exist");
|
|
62
|
+
|
|
63
|
+
if ("fallback" in props) {
|
|
64
|
+
return (
|
|
65
|
+
<React.Suspense fallback={!display_fallback ? null : props.fallback}>
|
|
66
|
+
<ReadCache cache={cacheRef.current}>{props.children}</ReadCache>
|
|
67
|
+
</React.Suspense>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return <ReadCache cache={cacheRef.current} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type ReadCacheProps = {
|
|
75
|
+
cache: Cache;
|
|
76
|
+
children?: React.ReactNode;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function ReadCache(props: ReadCacheProps) {
|
|
80
|
+
props.cache.read();
|
|
81
|
+
return props.children;
|
|
82
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
children: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Brackets(props: Props) {
|
|
12
|
+
const color = colors.orange;
|
|
13
|
+
const text_color = colors.blue;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Ink.Text color={text_color}>
|
|
17
|
+
<Ink.Text color={color}>[</Ink.Text>
|
|
18
|
+
{props.children}
|
|
19
|
+
<Ink.Text color={color}>]</Ink.Text>
|
|
20
|
+
</Ink.Text>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -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 = {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Command(props: Props) {
|
|
12
|
+
const text_color = colors.orange;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Ink.Text bold color={text_color}>
|
|
16
|
+
{props.children}
|
|
17
|
+
</Ink.Text>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import * as Ink from "ink-cjs";
|
|
7
|
+
|
|
8
|
+
import { Store } from "~/app/Store";
|
|
9
|
+
import { colors } from "~/core/colors";
|
|
10
|
+
import { invariant } from "~/core/invariant";
|
|
11
|
+
import * as json from "~/core/json";
|
|
12
|
+
|
|
13
|
+
export function Debug() {
|
|
14
|
+
const actions = Store.useActions();
|
|
15
|
+
const state = Store.useState((state) => state);
|
|
16
|
+
const argv = Store.useState((state) => state.argv);
|
|
17
|
+
const debug = Store.useState((state) => state.select.debug(state));
|
|
18
|
+
|
|
19
|
+
React.useEffect(
|
|
20
|
+
function debugMessageOnce() {
|
|
21
|
+
if (debug) {
|
|
22
|
+
actions.output(
|
|
23
|
+
<Ink.Text color={colors.yellow}>Debug mode enabled</Ink.Text>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
[argv]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
React.useEffect(
|
|
31
|
+
function syncStateJson() {
|
|
32
|
+
invariant(state.cwd, "state.cwd must exist");
|
|
33
|
+
|
|
34
|
+
if (!argv?.["write-state-json"]) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const output_file = path.join(state.cwd, "git-stack-state.json");
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(output_file)) {
|
|
41
|
+
fs.rmSync(output_file);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const serialized = json.serialize(state);
|
|
45
|
+
const content = JSON.stringify(serialized, null, 2);
|
|
46
|
+
fs.writeFileSync(output_file, content);
|
|
47
|
+
},
|
|
48
|
+
[argv, state]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { Await } from "~/app/Await";
|
|
6
|
+
import { Command } from "~/app/Command";
|
|
7
|
+
import { Parens } from "~/app/Parens";
|
|
8
|
+
import { Store } from "~/app/Store";
|
|
9
|
+
import { Url } from "~/app/Url";
|
|
10
|
+
import { cli } from "~/core/cli";
|
|
11
|
+
import { colors } from "~/core/colors";
|
|
12
|
+
import { is_command_available } from "~/core/is_command_available";
|
|
13
|
+
import { match_group } from "~/core/match_group";
|
|
14
|
+
import { semver_compare } from "~/core/semver_compare";
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function DependencyCheck(props: Props) {
|
|
21
|
+
const actions = Store.useActions();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Await
|
|
25
|
+
fallback={
|
|
26
|
+
<Ink.Text color={colors.yellow}>
|
|
27
|
+
Checking <Command>git</Command> install...
|
|
28
|
+
</Ink.Text>
|
|
29
|
+
}
|
|
30
|
+
function={async () => {
|
|
31
|
+
// await Promise.all([
|
|
32
|
+
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
33
|
+
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
34
|
+
// ]);
|
|
35
|
+
|
|
36
|
+
if (is_command_available("git")) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
actions.output(
|
|
41
|
+
<Ink.Text color={colors.yellow}>
|
|
42
|
+
<Command>git</Command> must be installed.
|
|
43
|
+
</Ink.Text>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
actions.exit(2);
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Await
|
|
50
|
+
fallback={
|
|
51
|
+
<Ink.Text color={colors.yellow}>
|
|
52
|
+
Checking <Command>node</Command> install...
|
|
53
|
+
</Ink.Text>
|
|
54
|
+
}
|
|
55
|
+
function={async () => {
|
|
56
|
+
const process_version = process.version.substring(1);
|
|
57
|
+
const semver_result = semver_compare(process_version, "14.0.0");
|
|
58
|
+
|
|
59
|
+
if (semver_result >= 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
actions.output(
|
|
64
|
+
<Ink.Text color={colors.yellow}>
|
|
65
|
+
<Command>node</Command> must be installed.
|
|
66
|
+
</Ink.Text>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
actions.exit(2);
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Await
|
|
73
|
+
fallback={
|
|
74
|
+
<Ink.Text color={colors.yellow}>
|
|
75
|
+
<Ink.Text>
|
|
76
|
+
Checking <Command>gh</Command> install...
|
|
77
|
+
</Ink.Text>
|
|
78
|
+
</Ink.Text>
|
|
79
|
+
}
|
|
80
|
+
function={async () => {
|
|
81
|
+
if (is_command_available("gh")) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
actions.output(
|
|
86
|
+
<Ink.Text color={colors.yellow}>
|
|
87
|
+
<Command>gh</Command> must be installed.
|
|
88
|
+
</Ink.Text>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
actions.output(
|
|
92
|
+
<Ink.Text color={colors.yellow}>
|
|
93
|
+
<Ink.Text>{"Visit "}</Ink.Text>
|
|
94
|
+
<Url>https://cli.github.com</Url>
|
|
95
|
+
<Ink.Text>{" to install the github cli "}</Ink.Text>
|
|
96
|
+
|
|
97
|
+
<Parens>
|
|
98
|
+
<Command>gh</Command>
|
|
99
|
+
</Parens>
|
|
100
|
+
</Ink.Text>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
actions.exit(3);
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<Await
|
|
107
|
+
fallback={
|
|
108
|
+
<Ink.Text color={colors.yellow}>
|
|
109
|
+
<Ink.Text>
|
|
110
|
+
Checking <Command>gh auth status</Command>...
|
|
111
|
+
</Ink.Text>
|
|
112
|
+
</Ink.Text>
|
|
113
|
+
}
|
|
114
|
+
function={async () => {
|
|
115
|
+
const auth_status = await cli(`gh auth status`, {
|
|
116
|
+
ignoreExitCode: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (auth_status.code === 0) {
|
|
120
|
+
const username = match_group(
|
|
121
|
+
auth_status.stdout,
|
|
122
|
+
RE.auth_username,
|
|
123
|
+
"username"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
actions.set((state) => {
|
|
127
|
+
state.username = username;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
actions.output(
|
|
134
|
+
<Ink.Text color={colors.yellow}>
|
|
135
|
+
<Command>gh</Command>
|
|
136
|
+
<Ink.Text>{" requires login, please run "}</Ink.Text>
|
|
137
|
+
<Command>gh auth login</Command>
|
|
138
|
+
</Ink.Text>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
actions.exit(4);
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{props.children}
|
|
145
|
+
</Await>
|
|
146
|
+
</Await>
|
|
147
|
+
</Await>
|
|
148
|
+
</Await>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const RE = {
|
|
153
|
+
// Logged in to github.com as magus
|
|
154
|
+
auth_username: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
155
|
+
};
|
package/src/app/Exit.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { Store } from "~/app/Store";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
clear: boolean;
|
|
7
|
+
code: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function Exit(props: Props) {
|
|
11
|
+
const actions = Store.useActions();
|
|
12
|
+
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (props.clear) {
|
|
15
|
+
actions.clear();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
actions.unmount();
|
|
19
|
+
|
|
20
|
+
process.exitCode = props.code;
|
|
21
|
+
process.exit();
|
|
22
|
+
}, [props.clear, props.code]);
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
message: string;
|
|
8
|
+
values: React.ComponentProps<typeof FormattedMessage>["values"];
|
|
9
|
+
wrapper?: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function FormatText(props: Props) {
|
|
13
|
+
const wrapper = (props.wrapper as React.ReactElement) || <Ink.Text />;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<FormattedMessage
|
|
17
|
+
id="FormatText"
|
|
18
|
+
defaultMessage={props.message}
|
|
19
|
+
values={props.values}
|
|
20
|
+
>
|
|
21
|
+
{(chunks) => {
|
|
22
|
+
return React.cloneElement(wrapper, {}, chunks);
|
|
23
|
+
}}
|
|
24
|
+
</FormattedMessage>
|
|
25
|
+
);
|
|
26
|
+
}
|