@flowscripter/dynamic-cli-framework 2.4.0 → 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/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.js +1 -1
- package/dist/src/service/spawn/DefaultSpawnService.js +6 -6
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +2 -2
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +33 -51
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +12 -9
- 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/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 +1 -1
- package/src/service/spawn/DefaultSpawnService.ts +6 -6
- package/src/service/upgrade/DefaultUpgradeService.ts +66 -65
- package/src/service/upgrade/UpgradeServiceProvider.ts +13 -9
|
@@ -2,7 +2,7 @@ import { UPGRADE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
2
2
|
import { PROMPTER_SERVICE_ID, PromptType } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
import { KEY_VALUE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
import {
|
|
5
|
+
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
6
|
import { Icon, PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
7
|
import DefaultUpgradeService from "./DefaultUpgradeService.js";
|
|
8
8
|
import { UpgradeSubCommand } from "./command/UpgradeSubCommand.js";
|
|
@@ -29,16 +29,19 @@ export default class UpgradeServiceProvider {
|
|
|
29
29
|
async initService(context) {
|
|
30
30
|
const upgradeService = this.#upgradeService;
|
|
31
31
|
const cliConfig = this.#cliConfig;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
else {
|
|
32
|
+
const spawnService = context.doesServiceExist(SPAWN_SERVICE_ID)
|
|
33
|
+
? context.getServiceById(SPAWN_SERVICE_ID)
|
|
34
|
+
: undefined;
|
|
35
|
+
const fetchService = context.doesServiceExist(FETCH_SERVICE_ID)
|
|
36
|
+
? context.getServiceById(FETCH_SERVICE_ID)
|
|
37
|
+
: undefined;
|
|
38
|
+
if (spawnService === undefined) {
|
|
40
39
|
logger.debug(() => "SpawnService not available, upgrade install methods will be unavailable");
|
|
41
40
|
}
|
|
41
|
+
if (fetchService === undefined) {
|
|
42
|
+
logger.debug(() => "FetchService not available, upgrade version checks will be unavailable");
|
|
43
|
+
}
|
|
44
|
+
upgradeService.setDependencies(spawnService, fetchService);
|
|
42
45
|
void upgradeService.getUpgradeCheckResult();
|
|
43
46
|
if (!context.doesServiceExist(PROMPTER_SERVICE_ID)) {
|
|
44
47
|
logger.debug(() => "PrompterService not available, skipping auto-upgrade");
|
package/index.ts
CHANGED
|
@@ -78,6 +78,11 @@ export type { ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
78
78
|
export { default as DefaultShutdownService } from "./src/service/shutdown/DefaultShutdownService.ts";
|
|
79
79
|
export { default as ShutdownServiceProvider } from "./src/service/shutdown/ShutdownServiceProvider.ts";
|
|
80
80
|
|
|
81
|
+
export { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
82
|
+
export type { FetchOptions, FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
83
|
+
export { default as DefaultFetchService } from "./src/service/fetch/DefaultFetchService.ts";
|
|
84
|
+
export { default as FetchServiceProvider } from "./src/service/fetch/FetchServiceProvider.ts";
|
|
85
|
+
|
|
81
86
|
export { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
82
87
|
export type {
|
|
83
88
|
SpawnOptions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowscripter/dynamic-cli-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"build": "tsc -p tsconfig.build.json"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@flowscripter/dynamic-cli-framework-api": "^1.
|
|
52
|
-
"@flowscripter/dynamic-plugin-framework": "1.
|
|
51
|
+
"@flowscripter/dynamic-cli-framework-api": "^1.4.0",
|
|
52
|
+
"@flowscripter/dynamic-plugin-framework": "1.12.0",
|
|
53
53
|
"emphasize": "^7.0.0",
|
|
54
54
|
"fastest-levenshtein": "^1.0.16",
|
|
55
55
|
"figlet": "^1.11.0",
|
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -52,6 +52,7 @@ import DefaultCompletionService from "../service/completion/DefaultCompletionSer
|
|
|
52
52
|
import CompletionServiceProvider from "../service/completion/CompletionServiceProvider.ts";
|
|
53
53
|
import ImagePrinterServiceProvider from "../service/imagePrinter/ImagePrinterServiceProvider.ts";
|
|
54
54
|
import SpawnServiceProvider from "../service/spawn/SpawnServiceProvider.ts";
|
|
55
|
+
import FetchServiceProvider from "../service/fetch/FetchServiceProvider.ts";
|
|
55
56
|
import UpgradeServiceProvider from "../service/upgrade/UpgradeServiceProvider.ts";
|
|
56
57
|
const logger = getLogger("BaseCLI");
|
|
57
58
|
|
|
@@ -72,6 +73,7 @@ const logger = getLogger("BaseCLI");
|
|
|
72
73
|
*
|
|
73
74
|
* * {@link ImagePrinterServiceProvider}
|
|
74
75
|
* * {@link SpawnServiceProvider}
|
|
76
|
+
* * {@link FetchServiceProvider}
|
|
75
77
|
* * {@link CompletionServiceProvider}
|
|
76
78
|
* * {@link UpgradeServiceProvider}
|
|
77
79
|
*
|
|
@@ -136,6 +138,7 @@ export default class BaseCLI implements CLI {
|
|
|
136
138
|
completionServiceEnabled: false,
|
|
137
139
|
imagePrinterServiceEnabled: false,
|
|
138
140
|
spawnServiceEnabled: false,
|
|
141
|
+
fetchServiceEnabled: false,
|
|
139
142
|
upgradeServiceEnabled: false,
|
|
140
143
|
upgradeLocationsConfig: { supportedPlatforms: [] },
|
|
141
144
|
validateAllCommands: false,
|
|
@@ -274,6 +277,10 @@ export default class BaseCLI implements CLI {
|
|
|
274
277
|
this.addServiceProvider(new SpawnServiceProvider(58));
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
if (this.#options.fetchServiceEnabled) {
|
|
281
|
+
this.addServiceProvider(new FetchServiceProvider(57));
|
|
282
|
+
}
|
|
283
|
+
|
|
277
284
|
if (this.#options.completionServiceEnabled) {
|
|
278
285
|
const completionService = new DefaultCompletionService();
|
|
279
286
|
this.addServiceProvider(
|
|
@@ -9,6 +9,7 @@ export default interface BaseCLIFeatureOptions {
|
|
|
9
9
|
readonly completionServiceEnabled?: boolean;
|
|
10
10
|
readonly imagePrinterServiceEnabled?: boolean;
|
|
11
11
|
readonly spawnServiceEnabled?: boolean;
|
|
12
|
+
readonly fetchServiceEnabled?: boolean;
|
|
12
13
|
readonly upgradeServiceEnabled?: boolean;
|
|
13
14
|
readonly upgradeLocationsConfig?: UpgradeLocationsConfig;
|
|
14
15
|
readonly validateAllCommands?: boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchOptions,
|
|
3
|
+
FetchService,
|
|
4
|
+
ShutdownService,
|
|
5
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import getLogger from "../../util/logger.ts";
|
|
7
|
+
|
|
8
|
+
const logger = getLogger("DefaultFetchService");
|
|
9
|
+
|
|
10
|
+
export default class DefaultFetchService implements FetchService {
|
|
11
|
+
#shutdownService: ShutdownService | undefined;
|
|
12
|
+
|
|
13
|
+
public setDependencies(shutdownService: ShutdownService): void {
|
|
14
|
+
this.#shutdownService = shutdownService;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async fetch(input: string | URL, options: FetchOptions = {}): Promise<Response> {
|
|
18
|
+
if (this.#shutdownService === undefined) {
|
|
19
|
+
throw new Error("DefaultFetchService.fetch() called before setDependencies()");
|
|
20
|
+
}
|
|
21
|
+
const shutdownService = this.#shutdownService;
|
|
22
|
+
const { timeoutMs, longRunning = false, signal: callerSignal, ...requestInit } = options;
|
|
23
|
+
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const signals: AbortSignal[] = [controller.signal];
|
|
26
|
+
if (callerSignal) {
|
|
27
|
+
signals.push(callerSignal);
|
|
28
|
+
}
|
|
29
|
+
if (timeoutMs !== undefined) {
|
|
30
|
+
signals.push(AbortSignal.timeout(timeoutMs));
|
|
31
|
+
}
|
|
32
|
+
const signal = signals.length === 1 ? signals[0]! : AbortSignal.any(signals);
|
|
33
|
+
|
|
34
|
+
let settled = false;
|
|
35
|
+
if (longRunning) {
|
|
36
|
+
shutdownService.enterLongRunningMode();
|
|
37
|
+
shutdownService.addShutdownListener(async () => {
|
|
38
|
+
if (settled) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
logger.debug(() => `Aborting fetch of '${input.toString()}' due to shutdown`);
|
|
42
|
+
controller.abort();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return await fetch(input, { ...requestInit, signal });
|
|
48
|
+
} finally {
|
|
49
|
+
settled = true;
|
|
50
|
+
if (longRunning) {
|
|
51
|
+
shutdownService.leaveLongRunningMode();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CLIConfig,
|
|
3
|
+
Context,
|
|
4
|
+
ServiceInfo,
|
|
5
|
+
ServiceProvider,
|
|
6
|
+
ShutdownService,
|
|
7
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
8
|
+
import { FETCH_SERVICE_ID, SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
9
|
+
import DefaultFetchService from "./DefaultFetchService.ts";
|
|
10
|
+
|
|
11
|
+
export default class FetchServiceProvider implements ServiceProvider {
|
|
12
|
+
readonly serviceId: string = FETCH_SERVICE_ID;
|
|
13
|
+
readonly servicePriority: number;
|
|
14
|
+
#fetchService: DefaultFetchService | undefined;
|
|
15
|
+
|
|
16
|
+
public constructor(servicePriority: number) {
|
|
17
|
+
this.servicePriority = servicePriority;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
21
|
+
this.#fetchService = new DefaultFetchService();
|
|
22
|
+
return Promise.resolve({
|
|
23
|
+
service: this.#fetchService,
|
|
24
|
+
commands: [],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public initService(context: Context): Promise<void> {
|
|
29
|
+
const shutdownService = context.getServiceById(SHUTDOWN_SERVICE_ID) as ShutdownService;
|
|
30
|
+
this.#fetchService!.setDependencies(shutdownService);
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -3,7 +3,10 @@ import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
3
3
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import type { PrinterService, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
5
|
import { PRINTER_SERVICE_ID, SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import type { FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
8
|
import type {
|
|
9
|
+
FetchCapable,
|
|
7
10
|
MarketplacePluginManager,
|
|
8
11
|
SpawnCapable,
|
|
9
12
|
} from "@flowscripter/dynamic-plugin-framework";
|
|
@@ -13,6 +16,9 @@ import { PluginGroupCommand } from "./command/PluginGroupCommand.ts";
|
|
|
13
16
|
import SpawnInterfaceAdapter, {
|
|
14
17
|
type SpawnInterfaceAdapterOptions,
|
|
15
18
|
} from "./SpawnInterfaceAdapter.ts";
|
|
19
|
+
import FetchInterfaceAdapter, {
|
|
20
|
+
type FetchInterfaceAdapterOptions,
|
|
21
|
+
} from "./FetchInterfaceAdapter.ts";
|
|
16
22
|
import getLogger from "../../util/logger.ts";
|
|
17
23
|
|
|
18
24
|
const logger = getLogger("DefaultPluginServiceProvider");
|
|
@@ -22,16 +28,19 @@ export default class DefaultPluginServiceProvider implements ServiceProvider {
|
|
|
22
28
|
readonly servicePriority: number;
|
|
23
29
|
readonly #pluginManager: MarketplacePluginManager;
|
|
24
30
|
readonly #spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions;
|
|
31
|
+
readonly #fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions;
|
|
25
32
|
#pluginService: DefaultPluginService | undefined;
|
|
26
33
|
|
|
27
34
|
constructor(
|
|
28
35
|
pluginManager: MarketplacePluginManager,
|
|
29
36
|
servicePriority = 50,
|
|
30
37
|
spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions = {},
|
|
38
|
+
fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions = {},
|
|
31
39
|
) {
|
|
32
40
|
this.#pluginManager = pluginManager;
|
|
33
41
|
this.servicePriority = servicePriority;
|
|
34
42
|
this.#spawnInterfaceAdapterOptions = spawnInterfaceAdapterOptions;
|
|
43
|
+
this.#fetchInterfaceAdapterOptions = fetchInterfaceAdapterOptions;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
async getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
@@ -45,21 +54,29 @@ export default class DefaultPluginServiceProvider implements ServiceProvider {
|
|
|
45
54
|
initService(context: Context): Promise<void> {
|
|
46
55
|
if (!context.doesServiceExist(SPAWN_SERVICE_ID)) {
|
|
47
56
|
logger.debug("SpawnService not available, plugin manager will spawn processes directly");
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
if (!("setSpawn" in this.#pluginManager)) {
|
|
57
|
+
} else if (!("setSpawn" in this.#pluginManager)) {
|
|
51
58
|
logger.debug("Plugin manager does not support SpawnCapable, skipping spawn injection");
|
|
52
|
-
|
|
59
|
+
} else {
|
|
60
|
+
const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
|
|
61
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
62
|
+
const adapter = new SpawnInterfaceAdapter(
|
|
63
|
+
spawnService,
|
|
64
|
+
printerService,
|
|
65
|
+
this.#spawnInterfaceAdapterOptions,
|
|
66
|
+
);
|
|
67
|
+
(this.#pluginManager as unknown as SpawnCapable).setSpawn(adapter);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!context.doesServiceExist(FETCH_SERVICE_ID)) {
|
|
71
|
+
logger.debug("FetchService not available, plugin manager will fetch directly");
|
|
72
|
+
} else if (!("setFetch" in this.#pluginManager)) {
|
|
73
|
+
logger.debug("Plugin manager does not support FetchCapable, skipping fetch injection");
|
|
74
|
+
} else {
|
|
75
|
+
const fetchService = context.getServiceById(FETCH_SERVICE_ID) as FetchService;
|
|
76
|
+
const adapter = new FetchInterfaceAdapter(fetchService, this.#fetchInterfaceAdapterOptions);
|
|
77
|
+
(this.#pluginManager as unknown as FetchCapable).setFetch(adapter);
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
|
|
56
|
-
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
57
|
-
const adapter = new SpawnInterfaceAdapter(
|
|
58
|
-
spawnService,
|
|
59
|
-
printerService,
|
|
60
|
-
this.#spawnInterfaceAdapterOptions,
|
|
61
|
-
);
|
|
62
|
-
(this.#pluginManager as unknown as SpawnCapable).setSpawn(adapter);
|
|
63
80
|
return Promise.resolve();
|
|
64
81
|
}
|
|
65
82
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { FetchInterface } from "@flowscripter/dynamic-plugin-framework";
|
|
3
|
+
|
|
4
|
+
export interface FetchInterfaceAdapterOptions {
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adapts a {@link FetchService} to dynamic-plugin-framework's {@link FetchInterface}, so plugin
|
|
10
|
+
* manager/repository fetches respect the host CLI's shutdown handling and default timeout.
|
|
11
|
+
*/
|
|
12
|
+
export default class FetchInterfaceAdapter implements FetchInterface {
|
|
13
|
+
readonly #fetchService: FetchService;
|
|
14
|
+
readonly #timeoutMs: number | undefined;
|
|
15
|
+
|
|
16
|
+
public constructor(fetchService: FetchService, options: FetchInterfaceAdapterOptions = {}) {
|
|
17
|
+
this.#fetchService = fetchService;
|
|
18
|
+
this.#timeoutMs = options.timeoutMs;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async fetch(
|
|
22
|
+
input: string,
|
|
23
|
+
init?: RequestInit & { timeoutMs?: number },
|
|
24
|
+
): Promise<Response> {
|
|
25
|
+
const { timeoutMs = this.#timeoutMs, ...requestInit } = init ?? {};
|
|
26
|
+
return this.#fetchService.fetch(input, { ...requestInit, timeoutMs });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -95,10 +95,10 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
95
95
|
}
|
|
96
96
|
const printerService = this.#printerService;
|
|
97
97
|
const shutdownService = this.#shutdownService;
|
|
98
|
-
const
|
|
98
|
+
const mode = options.mode ?? "inherit";
|
|
99
99
|
const longRunning = options.longRunning ?? true;
|
|
100
100
|
|
|
101
|
-
if (
|
|
101
|
+
if (mode === "inherit") {
|
|
102
102
|
await printerService.hideSpinner();
|
|
103
103
|
await printerService.hideAllProgressBars();
|
|
104
104
|
}
|
|
@@ -107,9 +107,9 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
107
107
|
try {
|
|
108
108
|
proc = Bun.spawn(resolveForPlatform(command), {
|
|
109
109
|
cwd: options.cwd,
|
|
110
|
-
stdin:
|
|
111
|
-
stdout:
|
|
112
|
-
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",
|
|
113
113
|
});
|
|
114
114
|
} catch (error) {
|
|
115
115
|
logger.debug(() => `Failed to launch command '${command.join(" ")}': ${error}`);
|
|
@@ -129,7 +129,7 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
129
129
|
await this.#terminate(proc, command);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
if (
|
|
132
|
+
if (mode === "wrapped" && options.onOutput) {
|
|
133
133
|
const onOutput = options.onOutput;
|
|
134
134
|
void this.#pipeLines(proc.stdout, "stdout", onOutput);
|
|
135
135
|
void this.#pipeLines(proc.stderr, "stderr", onOutput);
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import type {
|
|
6
6
|
CLIConfig,
|
|
7
|
-
|
|
7
|
+
FetchService,
|
|
8
8
|
SpawnResult,
|
|
9
9
|
SpawnService,
|
|
10
10
|
UpgradeCheckResult,
|
|
@@ -40,7 +40,7 @@ const OS_LABELS: Record<SupportedOs, string> = {
|
|
|
40
40
|
|
|
41
41
|
export default class DefaultUpgradeService implements UpgradeService {
|
|
42
42
|
#spawnService: SpawnService | undefined;
|
|
43
|
-
#
|
|
43
|
+
#fetchService: FetchService | undefined;
|
|
44
44
|
#upgradeCheckPromise: Promise<UpgradeCheckResult | undefined> | undefined;
|
|
45
45
|
readonly #config: UpgradeLocationsConfig;
|
|
46
46
|
readonly #cliConfig: CLIConfig;
|
|
@@ -50,9 +50,12 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
50
50
|
this.#cliConfig = cliConfig;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
public setDependencies(
|
|
53
|
+
public setDependencies(
|
|
54
|
+
spawnService: SpawnService | undefined,
|
|
55
|
+
fetchService: FetchService | undefined,
|
|
56
|
+
): void {
|
|
54
57
|
this.#spawnService = spawnService;
|
|
55
|
-
this.#
|
|
58
|
+
this.#fetchService = fetchService;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
public getUpgradeCheckResult(waitForResult = false): Promise<UpgradeCheckResult | undefined> {
|
|
@@ -170,6 +173,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
170
173
|
if (!this.#spawnService) {
|
|
171
174
|
return { ok: false, oldVersion, error: new Error("SpawnService is not available") };
|
|
172
175
|
}
|
|
176
|
+
if (checkResult.installMethod === InstallMethod.GITHUB_RELEASE && !this.#fetchService) {
|
|
177
|
+
return { ok: false, oldVersion, error: new Error("FetchService is not available") };
|
|
178
|
+
}
|
|
173
179
|
|
|
174
180
|
try {
|
|
175
181
|
switch (checkResult.installMethod) {
|
|
@@ -204,7 +210,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
204
210
|
}
|
|
205
211
|
const result = await this.#spawnService.spawn(
|
|
206
212
|
["brew", "list", "--versions", this.#config.homebrew.formula],
|
|
207
|
-
{
|
|
213
|
+
{ mode: "ignore", longRunning: false, timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
208
214
|
);
|
|
209
215
|
return result.ok;
|
|
210
216
|
}
|
|
@@ -215,7 +221,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
215
221
|
}
|
|
216
222
|
const result = await this.#spawnService.spawn(
|
|
217
223
|
["winget", "list", "--id", this.#config.winget.packageId],
|
|
218
|
-
{
|
|
224
|
+
{ mode: "ignore", longRunning: false, timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
219
225
|
);
|
|
220
226
|
return result.ok;
|
|
221
227
|
}
|
|
@@ -239,14 +245,14 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
239
245
|
}
|
|
240
246
|
|
|
241
247
|
async #getLatestGithubReleaseVersion(): Promise<string | undefined> {
|
|
242
|
-
if (!this.#config.githubRelease) {
|
|
248
|
+
if (!this.#config.githubRelease || !this.#fetchService) {
|
|
243
249
|
return undefined;
|
|
244
250
|
}
|
|
245
251
|
const { owner, repo } = this.#config.githubRelease;
|
|
246
252
|
try {
|
|
247
|
-
const response = await fetch(
|
|
253
|
+
const response = await this.#fetchService.fetch(
|
|
248
254
|
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
249
|
-
{
|
|
255
|
+
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
250
256
|
);
|
|
251
257
|
if (!response.ok) {
|
|
252
258
|
return undefined;
|
|
@@ -260,7 +266,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
async #getLatestHomebrewVersion(): Promise<string | undefined> {
|
|
263
|
-
if (!this.#config.homebrew) {
|
|
269
|
+
if (!this.#config.homebrew || !this.#fetchService) {
|
|
264
270
|
return undefined;
|
|
265
271
|
}
|
|
266
272
|
const { tap, formula } = this.#config.homebrew;
|
|
@@ -269,9 +275,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
269
275
|
return undefined;
|
|
270
276
|
}
|
|
271
277
|
try {
|
|
272
|
-
const response = await fetch(
|
|
278
|
+
const response = await this.#fetchService.fetch(
|
|
273
279
|
`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`,
|
|
274
|
-
{
|
|
280
|
+
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
275
281
|
);
|
|
276
282
|
if (!response.ok) {
|
|
277
283
|
return undefined;
|
|
@@ -292,7 +298,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
292
298
|
const result = await this.#spawnService.spawn(
|
|
293
299
|
["winget", "show", "--id", this.#config.winget.packageId],
|
|
294
300
|
{
|
|
295
|
-
|
|
301
|
+
mode: "wrapped",
|
|
296
302
|
longRunning: false,
|
|
297
303
|
timeoutMs: VERSION_CHECK_TIMEOUT_MS,
|
|
298
304
|
onOutput: (line) => lines.push(line),
|
|
@@ -312,7 +318,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
312
318
|
|
|
313
319
|
async #upgradeViaLinuxScript(): Promise<void> {
|
|
314
320
|
const { scriptUrl } = this.#config.linuxScript!;
|
|
315
|
-
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
|
+
});
|
|
316
324
|
if (!result.ok) {
|
|
317
325
|
throw new Error(`Install script failed: ${describeSpawnFailure(result)}`);
|
|
318
326
|
}
|
|
@@ -320,7 +328,9 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
320
328
|
|
|
321
329
|
async #upgradeViaHomebrew(): Promise<void> {
|
|
322
330
|
const { tap, formula } = this.#config.homebrew!;
|
|
323
|
-
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
|
+
});
|
|
324
334
|
if (!result.ok) {
|
|
325
335
|
throw new Error(`brew upgrade failed: ${describeSpawnFailure(result)}`);
|
|
326
336
|
}
|
|
@@ -328,15 +338,18 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
328
338
|
|
|
329
339
|
async #upgradeViaWinget(): Promise<void> {
|
|
330
340
|
const { packageId } = this.#config.winget!;
|
|
331
|
-
const result = await this.#spawnService!.spawn(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
+
);
|
|
340
353
|
if (!result.ok) {
|
|
341
354
|
throw new Error(`winget upgrade failed: ${describeSpawnFailure(result)}`);
|
|
342
355
|
}
|
|
@@ -349,19 +362,13 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
349
362
|
const assetName = assetPattern.replace("{os}", OS_LABELS[os]).replace("{arch}", archLabel);
|
|
350
363
|
const url = `https://github.com/${owner}/${repo}/releases/latest/download/${assetName}`;
|
|
351
364
|
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
this.#
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const response = await fetch(url);
|
|
358
|
-
if (!response.ok) {
|
|
359
|
-
throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
|
|
360
|
-
}
|
|
361
|
-
archiveData = await response.arrayBuffer();
|
|
362
|
-
} finally {
|
|
363
|
-
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}`);
|
|
364
370
|
}
|
|
371
|
+
const archiveData = await response.arrayBuffer();
|
|
365
372
|
|
|
366
373
|
const tmpDir = await mkdtemp(join(tmpdir(), "upgrade-"));
|
|
367
374
|
const archivePath = join(tmpDir, assetName);
|
|
@@ -370,52 +377,46 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
370
377
|
const currentExecutable = process.execPath;
|
|
371
378
|
|
|
372
379
|
if (os === SupportedOs.WINDOWS) {
|
|
373
|
-
const extractResult = await this.#spawnService!.spawn(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
+
);
|
|
378
388
|
if (!extractResult.ok) {
|
|
379
389
|
throw new Error("Failed to extract release archive");
|
|
380
390
|
}
|
|
381
391
|
const extractedBinary = join(tmpDir, `${this.#cliConfig.name}.exe`);
|
|
382
392
|
const oldPath = `${currentExecutable}.old.exe`;
|
|
383
|
-
const moveResult = await this.#spawnService!.spawn(
|
|
384
|
-
"cmd",
|
|
385
|
-
"
|
|
386
|
-
|
|
387
|
-
"/y",
|
|
388
|
-
currentExecutable,
|
|
389
|
-
oldPath,
|
|
390
|
-
]);
|
|
393
|
+
const moveResult = await this.#spawnService!.spawn(
|
|
394
|
+
["cmd", "/c", "move", "/y", currentExecutable, oldPath],
|
|
395
|
+
{ mode: "ignore" },
|
|
396
|
+
);
|
|
391
397
|
if (!moveResult.ok) {
|
|
392
398
|
throw new Error("Failed to move current executable aside");
|
|
393
399
|
}
|
|
394
|
-
const copyResult = await this.#spawnService!.spawn(
|
|
395
|
-
"cmd",
|
|
396
|
-
"
|
|
397
|
-
|
|
398
|
-
"/y",
|
|
399
|
-
extractedBinary,
|
|
400
|
-
currentExecutable,
|
|
401
|
-
]);
|
|
400
|
+
const copyResult = await this.#spawnService!.spawn(
|
|
401
|
+
["cmd", "/c", "copy", "/y", extractedBinary, currentExecutable],
|
|
402
|
+
{ mode: "ignore" },
|
|
403
|
+
);
|
|
402
404
|
if (!copyResult.ok) {
|
|
403
405
|
throw new Error("Failed to copy new executable into place");
|
|
404
406
|
}
|
|
405
407
|
} else {
|
|
406
|
-
const extractResult = await this.#spawnService!.spawn(
|
|
407
|
-
"unzip",
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
]);
|
|
408
|
+
const extractResult = await this.#spawnService!.spawn(
|
|
409
|
+
["unzip", "-o", archivePath, "-d", tmpDir],
|
|
410
|
+
{
|
|
411
|
+
mode: "ignore",
|
|
412
|
+
},
|
|
413
|
+
);
|
|
413
414
|
if (!extractResult.ok) {
|
|
414
415
|
throw new Error("Failed to extract release archive");
|
|
415
416
|
}
|
|
416
417
|
const extractedBinary = join(tmpDir, this.#cliConfig.name);
|
|
417
418
|
await Bun.write(currentExecutable, Bun.file(extractedBinary));
|
|
418
|
-
await this.#spawnService!.spawn(["chmod", "+x", currentExecutable]);
|
|
419
|
+
await this.#spawnService!.spawn(["chmod", "+x", currentExecutable], { mode: "ignore" });
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
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,19 @@ 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);
|
|
56
60
|
|
|
57
61
|
void upgradeService.getUpgradeCheckResult();
|
|
58
62
|
|