@flowscripter/dynamic-cli-framework 2.3.3 → 2.5.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/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/src/cli/BaseCLI.d.ts +1 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +6 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +1 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
- package/dist/src/command/VersionCommand.js +1 -1
- package/dist/src/service/banner/BannerServiceProvider.js +1 -1
- package/dist/src/service/fetch/DefaultFetchService.d.ts +7 -0
- package/dist/src/service/fetch/DefaultFetchService.d.ts.map +1 -0
- package/dist/src/service/fetch/DefaultFetchService.js +44 -0
- package/dist/src/service/fetch/FetchServiceProvider.d.ts +10 -0
- package/dist/src/service/fetch/FetchServiceProvider.d.ts.map +1 -0
- package/dist/src/service/fetch/FetchServiceProvider.js +22 -0
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts +2 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts.map +1 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.js +23 -8
- package/dist/src/service/plugin/FetchInterfaceAdapter.d.ts +17 -0
- package/dist/src/service/plugin/FetchInterfaceAdapter.d.ts.map +1 -0
- package/dist/src/service/plugin/FetchInterfaceAdapter.js +16 -0
- package/dist/src/service/plugin/SpawnInterfaceAdapter.d.ts.map +1 -1
- package/dist/src/service/plugin/SpawnInterfaceAdapter.js +6 -3
- package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -1
- package/dist/src/service/spawn/DefaultSpawnService.js +38 -16
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +4 -2
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +62 -75
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +14 -10
- package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts.map +1 -1
- package/dist/src/service/upgrade/command/UpgradeSubCommand.js +3 -1
- package/index.ts +5 -0
- package/package.json +3 -3
- package/src/cli/BaseCLI.ts +7 -0
- package/src/cli/BaseCLIFeatureOptions.ts +1 -0
- package/src/command/VersionCommand.ts +1 -1
- package/src/service/banner/BannerServiceProvider.ts +1 -1
- package/src/service/fetch/DefaultFetchService.ts +55 -0
- package/src/service/fetch/FetchServiceProvider.ts +33 -0
- package/src/service/plugin/DefaultPluginServiceProvider.ts +29 -12
- package/src/service/plugin/FetchInterfaceAdapter.ts +28 -0
- package/src/service/plugin/SpawnInterfaceAdapter.ts +6 -3
- package/src/service/spawn/DefaultSpawnService.ts +45 -16
- package/src/service/upgrade/DefaultUpgradeService.ts +107 -102
- package/src/service/upgrade/UpgradeServiceProvider.ts +16 -10
- package/src/service/upgrade/command/UpgradeSubCommand.ts +4 -1
|
@@ -45,7 +45,7 @@ export default class SpawnInterfaceAdapter implements SpawnInterface {
|
|
|
45
45
|
|
|
46
46
|
const result = await this.#spawnService.spawn(command, {
|
|
47
47
|
cwd: options.cwd,
|
|
48
|
-
|
|
48
|
+
mode: "wrapped",
|
|
49
49
|
onOutput,
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -55,8 +55,11 @@ export default class SpawnInterfaceAdapter implements SpawnInterface {
|
|
|
55
55
|
this.#printerService.endMark();
|
|
56
56
|
await this.#printerService.clearMarked(this.#markMinimumDisplayTimeMs);
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
if (result.ok) {
|
|
59
|
+
return { ok: true, exitCode: result.exitCode };
|
|
60
|
+
}
|
|
61
|
+
return "timedOut" in result
|
|
62
|
+
? { ok: false, error: new Error("Command timed out") }
|
|
60
63
|
: { ok: false, exitCode: result.exitCode, error: result.error };
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -41,6 +41,22 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
41
41
|
this.#shutdownService = shutdownService;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
async #terminate(
|
|
45
|
+
proc: ReturnType<typeof Bun.spawn>,
|
|
46
|
+
command: ReadonlyArray<string>,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
logger.debug(() => `Sending SIGTERM to spawned command '${command.join(" ")}'`);
|
|
49
|
+
proc.kill("SIGTERM");
|
|
50
|
+
const exitedInTime = await Promise.race([
|
|
51
|
+
proc.exited.then(() => true),
|
|
52
|
+
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), SHUTDOWN_GRACE_PERIOD_MS)),
|
|
53
|
+
]);
|
|
54
|
+
if (!exitedInTime) {
|
|
55
|
+
logger.debug(() => `Sending SIGKILL to spawned command '${command.join(" ")}'`);
|
|
56
|
+
proc.kill("SIGKILL");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
async #pipeLines(
|
|
45
61
|
stream: ReadableStream<Uint8Array> | number | undefined,
|
|
46
62
|
streamName: "stdout" | "stderr",
|
|
@@ -79,10 +95,10 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
79
95
|
}
|
|
80
96
|
const printerService = this.#printerService;
|
|
81
97
|
const shutdownService = this.#shutdownService;
|
|
82
|
-
const
|
|
98
|
+
const mode = options.mode ?? "inherit";
|
|
83
99
|
const longRunning = options.longRunning ?? true;
|
|
84
100
|
|
|
85
|
-
if (
|
|
101
|
+
if (mode === "inherit") {
|
|
86
102
|
await printerService.hideSpinner();
|
|
87
103
|
await printerService.hideAllProgressBars();
|
|
88
104
|
}
|
|
@@ -91,9 +107,9 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
91
107
|
try {
|
|
92
108
|
proc = Bun.spawn(resolveForPlatform(command), {
|
|
93
109
|
cwd: options.cwd,
|
|
94
|
-
stdin:
|
|
95
|
-
stdout:
|
|
96
|
-
stderr:
|
|
110
|
+
stdin: mode === "inherit" ? "inherit" : "ignore",
|
|
111
|
+
stdout: mode === "wrapped" ? "pipe" : mode === "ignore" ? "ignore" : "inherit",
|
|
112
|
+
stderr: mode === "wrapped" ? "pipe" : mode === "ignore" ? "ignore" : "inherit",
|
|
97
113
|
});
|
|
98
114
|
} catch (error) {
|
|
99
115
|
logger.debug(() => `Failed to launch command '${command.join(" ")}': ${error}`);
|
|
@@ -110,25 +126,38 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
110
126
|
if (settled) {
|
|
111
127
|
return;
|
|
112
128
|
}
|
|
113
|
-
|
|
114
|
-
proc.kill("SIGTERM");
|
|
115
|
-
await Promise.race([
|
|
116
|
-
proc.exited.then(() => {}),
|
|
117
|
-
new Promise<void>((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_PERIOD_MS)),
|
|
118
|
-
]);
|
|
119
|
-
if (!settled) {
|
|
120
|
-
logger.debug(() => `Sending SIGKILL to spawned command '${command.join(" ")}'`);
|
|
121
|
-
proc.kill("SIGKILL");
|
|
122
|
-
}
|
|
129
|
+
await this.#terminate(proc, command);
|
|
123
130
|
});
|
|
124
131
|
|
|
125
|
-
if (
|
|
132
|
+
if (mode === "wrapped" && options.onOutput) {
|
|
126
133
|
const onOutput = options.onOutput;
|
|
127
134
|
void this.#pipeLines(proc.stdout, "stdout", onOutput);
|
|
128
135
|
void this.#pipeLines(proc.stderr, "stderr", onOutput);
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
try {
|
|
139
|
+
if (options.timeoutMs === undefined) {
|
|
140
|
+
const exitCode = await proc.exited;
|
|
141
|
+
settled = true;
|
|
142
|
+
return exitCode === 0 ? { ok: true, exitCode } : { ok: false, exitCode };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let timeoutHandle: ReturnType<typeof setTimeout>;
|
|
146
|
+
const timedOut = await Promise.race([
|
|
147
|
+
proc.exited.then(() => false),
|
|
148
|
+
new Promise<boolean>((resolve) => {
|
|
149
|
+
timeoutHandle = setTimeout(() => resolve(true), options.timeoutMs);
|
|
150
|
+
}),
|
|
151
|
+
]);
|
|
152
|
+
clearTimeout(timeoutHandle!);
|
|
153
|
+
|
|
154
|
+
if (timedOut) {
|
|
155
|
+
logger.debug(() => `Command '${command.join(" ")}' timed out after ${options.timeoutMs}ms`);
|
|
156
|
+
await this.#terminate(proc, command);
|
|
157
|
+
settled = true;
|
|
158
|
+
return { ok: false, timedOut: true };
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
const exitCode = await proc.exited;
|
|
133
162
|
settled = true;
|
|
134
163
|
return exitCode === 0 ? { ok: true, exitCode } : { ok: false, exitCode };
|
|
@@ -4,7 +4,8 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import type {
|
|
6
6
|
CLIConfig,
|
|
7
|
-
|
|
7
|
+
FetchService,
|
|
8
|
+
SpawnResult,
|
|
8
9
|
SpawnService,
|
|
9
10
|
UpgradeCheckResult,
|
|
10
11
|
UpgradeResult,
|
|
@@ -22,18 +23,13 @@ import getLogger from "../../util/logger.ts";
|
|
|
22
23
|
const logger = getLogger("DefaultUpgradeService");
|
|
23
24
|
|
|
24
25
|
// checkForUpgrade() runs opportunistically on every CLI invocation (via BannerServiceProvider),
|
|
25
|
-
// so its version-check network calls must never be allowed to stall CLI startup.
|
|
26
|
-
const VERSION_CHECK_TIMEOUT_MS = 250;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
async function withSpawnTimeout<T>(spawnPromise: Promise<T>, fallback: T): Promise<T> {
|
|
33
|
-
return Promise.race([
|
|
34
|
-
spawnPromise,
|
|
35
|
-
new Promise<T>((resolve) => setTimeout(() => resolve(fallback), VERSION_CHECK_TIMEOUT_MS)),
|
|
36
|
-
]);
|
|
26
|
+
// so its version-check network/spawn calls must never be allowed to stall CLI startup.
|
|
27
|
+
export const VERSION_CHECK_TIMEOUT_MS = 250;
|
|
28
|
+
|
|
29
|
+
function describeSpawnFailure(result: Extract<SpawnResult, { ok: false }>): string {
|
|
30
|
+
return "timedOut" in result
|
|
31
|
+
? "timed out"
|
|
32
|
+
: (result.error?.message ?? `exit code ${result.exitCode}`);
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
const OS_LABELS: Record<SupportedOs, string> = {
|
|
@@ -44,7 +40,8 @@ const OS_LABELS: Record<SupportedOs, string> = {
|
|
|
44
40
|
|
|
45
41
|
export default class DefaultUpgradeService implements UpgradeService {
|
|
46
42
|
#spawnService: SpawnService | undefined;
|
|
47
|
-
#
|
|
43
|
+
#fetchService: FetchService | undefined;
|
|
44
|
+
#upgradeCheckPromise: Promise<UpgradeCheckResult | undefined> | undefined;
|
|
48
45
|
readonly #config: UpgradeLocationsConfig;
|
|
49
46
|
readonly #cliConfig: CLIConfig;
|
|
50
47
|
|
|
@@ -53,9 +50,27 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
53
50
|
this.#cliConfig = cliConfig;
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
public setDependencies(
|
|
53
|
+
public setDependencies(
|
|
54
|
+
spawnService: SpawnService | undefined,
|
|
55
|
+
fetchService: FetchService | undefined,
|
|
56
|
+
): void {
|
|
57
57
|
this.#spawnService = spawnService;
|
|
58
|
-
this.#
|
|
58
|
+
this.#fetchService = fetchService;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public getUpgradeCheckResult(waitForResult = false): Promise<UpgradeCheckResult | undefined> {
|
|
62
|
+
if (!this.#upgradeCheckPromise) {
|
|
63
|
+
this.#upgradeCheckPromise = this.checkForUpgrade();
|
|
64
|
+
}
|
|
65
|
+
if (waitForResult) {
|
|
66
|
+
return this.#upgradeCheckPromise;
|
|
67
|
+
}
|
|
68
|
+
return Promise.race([
|
|
69
|
+
this.#upgradeCheckPromise,
|
|
70
|
+
new Promise<undefined>((resolve) =>
|
|
71
|
+
setTimeout(() => resolve(undefined), VERSION_CHECK_TIMEOUT_MS),
|
|
72
|
+
),
|
|
73
|
+
]);
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
public detectOs(): SupportedOs | undefined {
|
|
@@ -143,7 +158,11 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
143
158
|
installMethodOverride?: InstallMethod,
|
|
144
159
|
): Promise<UpgradeResult> {
|
|
145
160
|
const oldVersion = this.#cliConfig.version;
|
|
146
|
-
const
|
|
161
|
+
const hasOverride =
|
|
162
|
+
osOverride !== undefined || archOverride !== undefined || installMethodOverride !== undefined;
|
|
163
|
+
const checkResult = hasOverride
|
|
164
|
+
? await this.checkForUpgrade(osOverride, archOverride, installMethodOverride)
|
|
165
|
+
: await this.getUpgradeCheckResult(true);
|
|
147
166
|
if (!checkResult) {
|
|
148
167
|
return {
|
|
149
168
|
ok: false,
|
|
@@ -154,6 +173,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
154
173
|
if (!this.#spawnService) {
|
|
155
174
|
return { ok: false, oldVersion, error: new Error("SpawnService is not available") };
|
|
156
175
|
}
|
|
176
|
+
if (checkResult.installMethod === InstallMethod.GITHUB_RELEASE && !this.#fetchService) {
|
|
177
|
+
return { ok: false, oldVersion, error: new Error("FetchService is not available") };
|
|
178
|
+
}
|
|
157
179
|
|
|
158
180
|
try {
|
|
159
181
|
switch (checkResult.installMethod) {
|
|
@@ -186,12 +208,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
186
208
|
if (!this.#spawnService || !this.#config.homebrew) {
|
|
187
209
|
return false;
|
|
188
210
|
}
|
|
189
|
-
const result = await
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
longRunning: false,
|
|
193
|
-
}),
|
|
194
|
-
{ ok: false } as const,
|
|
211
|
+
const result = await this.#spawnService.spawn(
|
|
212
|
+
["brew", "list", "--versions", this.#config.homebrew.formula],
|
|
213
|
+
{ mode: "ignore", longRunning: false, timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
195
214
|
);
|
|
196
215
|
return result.ok;
|
|
197
216
|
}
|
|
@@ -200,12 +219,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
200
219
|
if (!this.#spawnService || !this.#config.winget) {
|
|
201
220
|
return false;
|
|
202
221
|
}
|
|
203
|
-
const result = await
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
longRunning: false,
|
|
207
|
-
}),
|
|
208
|
-
{ ok: false } as const,
|
|
222
|
+
const result = await this.#spawnService.spawn(
|
|
223
|
+
["winget", "list", "--id", this.#config.winget.packageId],
|
|
224
|
+
{ mode: "ignore", longRunning: false, timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
209
225
|
);
|
|
210
226
|
return result.ok;
|
|
211
227
|
}
|
|
@@ -229,14 +245,14 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
async #getLatestGithubReleaseVersion(): Promise<string | undefined> {
|
|
232
|
-
if (!this.#config.githubRelease) {
|
|
248
|
+
if (!this.#config.githubRelease || !this.#fetchService) {
|
|
233
249
|
return undefined;
|
|
234
250
|
}
|
|
235
251
|
const { owner, repo } = this.#config.githubRelease;
|
|
236
252
|
try {
|
|
237
|
-
const response = await fetch(
|
|
253
|
+
const response = await this.#fetchService.fetch(
|
|
238
254
|
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
239
|
-
{
|
|
255
|
+
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
240
256
|
);
|
|
241
257
|
if (!response.ok) {
|
|
242
258
|
return undefined;
|
|
@@ -250,7 +266,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
250
266
|
}
|
|
251
267
|
|
|
252
268
|
async #getLatestHomebrewVersion(): Promise<string | undefined> {
|
|
253
|
-
if (!this.#config.homebrew) {
|
|
269
|
+
if (!this.#config.homebrew || !this.#fetchService) {
|
|
254
270
|
return undefined;
|
|
255
271
|
}
|
|
256
272
|
const { tap, formula } = this.#config.homebrew;
|
|
@@ -259,9 +275,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
259
275
|
return undefined;
|
|
260
276
|
}
|
|
261
277
|
try {
|
|
262
|
-
const response = await fetch(
|
|
278
|
+
const response = await this.#fetchService.fetch(
|
|
263
279
|
`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`,
|
|
264
|
-
{
|
|
280
|
+
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
265
281
|
);
|
|
266
282
|
if (!response.ok) {
|
|
267
283
|
return undefined;
|
|
@@ -279,13 +295,14 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
279
295
|
return undefined;
|
|
280
296
|
}
|
|
281
297
|
const lines: string[] = [];
|
|
282
|
-
const result = await
|
|
283
|
-
|
|
284
|
-
|
|
298
|
+
const result = await this.#spawnService.spawn(
|
|
299
|
+
["winget", "show", "--id", this.#config.winget.packageId],
|
|
300
|
+
{
|
|
301
|
+
mode: "wrapped",
|
|
285
302
|
longRunning: false,
|
|
303
|
+
timeoutMs: VERSION_CHECK_TIMEOUT_MS,
|
|
286
304
|
onOutput: (line) => lines.push(line),
|
|
287
|
-
}
|
|
288
|
-
{ ok: false } as const,
|
|
305
|
+
},
|
|
289
306
|
);
|
|
290
307
|
if (!result.ok) {
|
|
291
308
|
return undefined;
|
|
@@ -301,39 +318,40 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
301
318
|
|
|
302
319
|
async #upgradeViaLinuxScript(): Promise<void> {
|
|
303
320
|
const { scriptUrl } = this.#config.linuxScript!;
|
|
304
|
-
const result = await this.#spawnService!.spawn(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`]
|
|
321
|
+
const result = await this.#spawnService!.spawn(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`], {
|
|
322
|
+
mode: "ignore",
|
|
323
|
+
});
|
|
305
324
|
if (!result.ok) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
`Install script failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`,
|
|
308
|
-
);
|
|
325
|
+
throw new Error(`Install script failed: ${describeSpawnFailure(result)}`);
|
|
309
326
|
}
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
async #upgradeViaHomebrew(): Promise<void> {
|
|
313
330
|
const { tap, formula } = this.#config.homebrew!;
|
|
314
|
-
const result = await this.#spawnService!.spawn(["brew", "upgrade", `${tap}/${formula}`]
|
|
331
|
+
const result = await this.#spawnService!.spawn(["brew", "upgrade", `${tap}/${formula}`], {
|
|
332
|
+
mode: "ignore",
|
|
333
|
+
});
|
|
315
334
|
if (!result.ok) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
`brew upgrade failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`,
|
|
318
|
-
);
|
|
335
|
+
throw new Error(`brew upgrade failed: ${describeSpawnFailure(result)}`);
|
|
319
336
|
}
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
async #upgradeViaWinget(): Promise<void> {
|
|
323
340
|
const { packageId } = this.#config.winget!;
|
|
324
|
-
const result = await this.#spawnService!.spawn(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
341
|
+
const result = await this.#spawnService!.spawn(
|
|
342
|
+
[
|
|
343
|
+
"winget",
|
|
344
|
+
"upgrade",
|
|
345
|
+
"--id",
|
|
346
|
+
packageId,
|
|
347
|
+
"--silent",
|
|
348
|
+
"--accept-package-agreements",
|
|
349
|
+
"--accept-source-agreements",
|
|
350
|
+
],
|
|
351
|
+
{ mode: "ignore" },
|
|
352
|
+
);
|
|
333
353
|
if (!result.ok) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
`winget upgrade failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`,
|
|
336
|
-
);
|
|
354
|
+
throw new Error(`winget upgrade failed: ${describeSpawnFailure(result)}`);
|
|
337
355
|
}
|
|
338
356
|
}
|
|
339
357
|
|
|
@@ -344,20 +362,13 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
344
362
|
const assetName = assetPattern.replace("{os}", OS_LABELS[os]).replace("{arch}", archLabel);
|
|
345
363
|
const url = `https://github.com/${owner}/${repo}/releases/latest/download/${assetName}`;
|
|
346
364
|
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
const response = await fetch(url);
|
|
354
|
-
if (!response.ok) {
|
|
355
|
-
throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
|
|
356
|
-
}
|
|
357
|
-
archiveData = await response.arrayBuffer();
|
|
358
|
-
} finally {
|
|
359
|
-
this.#shutdownService?.leaveLongRunningMode();
|
|
365
|
+
// longRunning: true gets cooperative Ctrl-C handling during what can be the slowest step of
|
|
366
|
+
// the upgrade.
|
|
367
|
+
const response = await this.#fetchService!.fetch(url, { longRunning: true });
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
|
|
360
370
|
}
|
|
371
|
+
const archiveData = await response.arrayBuffer();
|
|
361
372
|
|
|
362
373
|
const tmpDir = await mkdtemp(join(tmpdir(), "upgrade-"));
|
|
363
374
|
const archivePath = join(tmpDir, assetName);
|
|
@@ -366,52 +377,46 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
366
377
|
const currentExecutable = process.execPath;
|
|
367
378
|
|
|
368
379
|
if (os === SupportedOs.WINDOWS) {
|
|
369
|
-
const extractResult = await this.#spawnService!.spawn(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
380
|
+
const extractResult = await this.#spawnService!.spawn(
|
|
381
|
+
[
|
|
382
|
+
"powershell",
|
|
383
|
+
"-Command",
|
|
384
|
+
`Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}' -Force`,
|
|
385
|
+
],
|
|
386
|
+
{ mode: "ignore" },
|
|
387
|
+
);
|
|
374
388
|
if (!extractResult.ok) {
|
|
375
389
|
throw new Error("Failed to extract release archive");
|
|
376
390
|
}
|
|
377
391
|
const extractedBinary = join(tmpDir, `${this.#cliConfig.name}.exe`);
|
|
378
392
|
const oldPath = `${currentExecutable}.old.exe`;
|
|
379
|
-
const moveResult = await this.#spawnService!.spawn(
|
|
380
|
-
"cmd",
|
|
381
|
-
"
|
|
382
|
-
|
|
383
|
-
"/y",
|
|
384
|
-
currentExecutable,
|
|
385
|
-
oldPath,
|
|
386
|
-
]);
|
|
393
|
+
const moveResult = await this.#spawnService!.spawn(
|
|
394
|
+
["cmd", "/c", "move", "/y", currentExecutable, oldPath],
|
|
395
|
+
{ mode: "ignore" },
|
|
396
|
+
);
|
|
387
397
|
if (!moveResult.ok) {
|
|
388
398
|
throw new Error("Failed to move current executable aside");
|
|
389
399
|
}
|
|
390
|
-
const copyResult = await this.#spawnService!.spawn(
|
|
391
|
-
"cmd",
|
|
392
|
-
"
|
|
393
|
-
|
|
394
|
-
"/y",
|
|
395
|
-
extractedBinary,
|
|
396
|
-
currentExecutable,
|
|
397
|
-
]);
|
|
400
|
+
const copyResult = await this.#spawnService!.spawn(
|
|
401
|
+
["cmd", "/c", "copy", "/y", extractedBinary, currentExecutable],
|
|
402
|
+
{ mode: "ignore" },
|
|
403
|
+
);
|
|
398
404
|
if (!copyResult.ok) {
|
|
399
405
|
throw new Error("Failed to copy new executable into place");
|
|
400
406
|
}
|
|
401
407
|
} else {
|
|
402
|
-
const extractResult = await this.#spawnService!.spawn(
|
|
403
|
-
"unzip",
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
]);
|
|
408
|
+
const extractResult = await this.#spawnService!.spawn(
|
|
409
|
+
["unzip", "-o", archivePath, "-d", tmpDir],
|
|
410
|
+
{
|
|
411
|
+
mode: "ignore",
|
|
412
|
+
},
|
|
413
|
+
);
|
|
409
414
|
if (!extractResult.ok) {
|
|
410
415
|
throw new Error("Failed to extract release archive");
|
|
411
416
|
}
|
|
412
417
|
const extractedBinary = join(tmpDir, this.#cliConfig.name);
|
|
413
418
|
await Bun.write(currentExecutable, Bun.file(extractedBinary));
|
|
414
|
-
await this.#spawnService!.spawn(["chmod", "+x", currentExecutable]);
|
|
419
|
+
await this.#spawnService!.spawn(["chmod", "+x", currentExecutable], { mode: "ignore" });
|
|
415
420
|
}
|
|
416
421
|
}
|
|
417
422
|
}
|
|
@@ -8,8 +8,8 @@ import { KEY_VALUE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
8
8
|
import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
|
|
9
9
|
import { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
10
10
|
import type { SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
11
|
-
import {
|
|
12
|
-
import type {
|
|
11
|
+
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
12
|
+
import type { FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
13
13
|
import { Icon, PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
14
14
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
15
15
|
import DefaultUpgradeService from "./DefaultUpgradeService.ts";
|
|
@@ -44,15 +44,21 @@ export default class UpgradeServiceProvider implements ServiceProvider {
|
|
|
44
44
|
const upgradeService = this.#upgradeService!;
|
|
45
45
|
const cliConfig = this.#cliConfig!;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const spawnService = context.doesServiceExist(SPAWN_SERVICE_ID)
|
|
48
|
+
? (context.getServiceById(SPAWN_SERVICE_ID) as SpawnService)
|
|
49
|
+
: undefined;
|
|
50
|
+
const fetchService = context.doesServiceExist(FETCH_SERVICE_ID)
|
|
51
|
+
? (context.getServiceById(FETCH_SERVICE_ID) as FetchService)
|
|
52
|
+
: undefined;
|
|
53
|
+
if (spawnService === undefined) {
|
|
54
54
|
logger.debug(() => "SpawnService not available, upgrade install methods will be unavailable");
|
|
55
55
|
}
|
|
56
|
+
if (fetchService === undefined) {
|
|
57
|
+
logger.debug(() => "FetchService not available, upgrade version checks will be unavailable");
|
|
58
|
+
}
|
|
59
|
+
upgradeService.setDependencies(spawnService, fetchService);
|
|
60
|
+
|
|
61
|
+
void upgradeService.getUpgradeCheckResult();
|
|
56
62
|
|
|
57
63
|
if (!context.doesServiceExist(PROMPTER_SERVICE_ID)) {
|
|
58
64
|
logger.debug(() => "PrompterService not available, skipping auto-upgrade");
|
|
@@ -128,7 +134,7 @@ export default class UpgradeServiceProvider implements ServiceProvider {
|
|
|
128
134
|
cliConfig: CLIConfig,
|
|
129
135
|
): Promise<void> {
|
|
130
136
|
try {
|
|
131
|
-
const checkResult = await upgradeService.
|
|
137
|
+
const checkResult = await upgradeService.getUpgradeCheckResult();
|
|
132
138
|
if (!checkResult?.updateAvailable) {
|
|
133
139
|
return;
|
|
134
140
|
}
|
|
@@ -52,7 +52,10 @@ export class UpgradeSubCommand implements SubCommand {
|
|
|
52
52
|
const arch = undefined as SupportedArch | undefined;
|
|
53
53
|
const installMethod = argumentValues["install-method"] as InstallMethod | undefined;
|
|
54
54
|
|
|
55
|
-
const checkResult =
|
|
55
|
+
const checkResult =
|
|
56
|
+
os === undefined && installMethod === undefined
|
|
57
|
+
? await this.#upgradeService.getUpgradeCheckResult(true)
|
|
58
|
+
: await this.#upgradeService.checkForUpgrade(os, arch, installMethod);
|
|
56
59
|
if (!checkResult) {
|
|
57
60
|
await printerService.error(
|
|
58
61
|
`No upgrade location is configured for the detected or requested platform.\n`,
|