@flowscripter/dynamic-cli-framework 2.3.1 → 2.3.2

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.
@@ -1,10 +1,10 @@
1
- import type { CLIConfig, SpawnService, UpgradeCheckResult, UpgradeResult } from "@flowscripter/dynamic-cli-framework-api";
1
+ import type { CLIConfig, ShutdownService, SpawnService, UpgradeCheckResult, UpgradeResult } from "@flowscripter/dynamic-cli-framework-api";
2
2
  import { InstallMethod, SupportedArch, SupportedOs, type UpgradeService } from "@flowscripter/dynamic-cli-framework-api";
3
3
  import type { UpgradeLocationsConfig } from "./UpgradeLocationsConfig.ts";
4
4
  export default class DefaultUpgradeService implements UpgradeService {
5
5
  #private;
6
6
  constructor(config: UpgradeLocationsConfig, cliConfig: CLIConfig);
7
- setDependencies(spawnService: SpawnService): void;
7
+ setDependencies(spawnService: SpawnService, shutdownService?: ShutdownService): void;
8
8
  detectOs(): SupportedOs | undefined;
9
9
  detectArch(): SupportedArch | undefined;
10
10
  detectInstallMethod(os: SupportedOs): Promise<InstallMethod | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultUpgradeService.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/DefaultUpgradeService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACd,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,KAAK,cAAc,EACpB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAW1E,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;gBAK/C,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS;IAKhE,eAAe,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAIjD,QAAQ,IAAI,WAAW,GAAG,SAAS;IAanC,UAAU,IAAI,aAAa,GAAG,SAAS;IAWjC,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAgBxE,eAAe,CAC1B,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAmC7B,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC;CA6P1B"}
1
+ {"version":3,"file":"DefaultUpgradeService.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/DefaultUpgradeService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACd,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,KAAK,cAAc,EACpB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAe1E,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;gBAM/C,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS;IAKhE,eAAe,CAAC,YAAY,EAAE,YAAY,EAAE,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI;IAKpF,QAAQ,IAAI,WAAW,GAAG,SAAS;IAanC,UAAU,IAAI,aAAa,GAAG,SAAS;IAWjC,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAgBxE,eAAe,CAC1B,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAmC7B,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC;CA2Q1B"}
@@ -6,6 +6,9 @@ import { InstallMethod, SupportedArch, SupportedOs, } from "@flowscripter/dynami
6
6
  import semver from "semver";
7
7
  import getLogger from "../../util/logger.js";
8
8
  const logger = getLogger("DefaultUpgradeService");
9
+ // checkForUpgrade() runs opportunistically on every CLI invocation (via BannerServiceProvider),
10
+ // so its version-check network calls must never be allowed to stall CLI startup.
11
+ const VERSION_CHECK_TIMEOUT_MS = 250;
9
12
  const OS_LABELS = {
10
13
  [SupportedOs.LINUX]: "Linux",
11
14
  [SupportedOs.MACOS]: "MacOS",
@@ -13,14 +16,16 @@ const OS_LABELS = {
13
16
  };
14
17
  export default class DefaultUpgradeService {
15
18
  #spawnService;
19
+ #shutdownService;
16
20
  #config;
17
21
  #cliConfig;
18
22
  constructor(config, cliConfig) {
19
23
  this.#config = config;
20
24
  this.#cliConfig = cliConfig;
21
25
  }
22
- setDependencies(spawnService) {
26
+ setDependencies(spawnService, shutdownService) {
23
27
  this.#spawnService = spawnService;
28
+ this.#shutdownService = shutdownService;
24
29
  }
25
30
  detectOs() {
26
31
  switch (process.platform) {
@@ -162,7 +167,7 @@ export default class DefaultUpgradeService {
162
167
  }
163
168
  const { owner, repo } = this.#config.githubRelease;
164
169
  try {
165
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
170
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`, { signal: AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS) });
166
171
  if (!response.ok) {
167
172
  return undefined;
168
173
  }
@@ -184,7 +189,7 @@ export default class DefaultUpgradeService {
184
189
  return undefined;
185
190
  }
186
191
  try {
187
- const response = await fetch(`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`);
192
+ const response = await fetch(`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`, { signal: AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS) });
188
193
  if (!response.ok) {
189
194
  return undefined;
190
195
  }
@@ -251,13 +256,24 @@ export default class DefaultUpgradeService {
251
256
  const archLabel = os === SupportedOs.MACOS ? "aarch64" : arch === SupportedArch.X64 ? "x64" : "arm64";
252
257
  const assetName = assetPattern.replace("{os}", OS_LABELS[os]).replace("{arch}", archLabel);
253
258
  const url = `https://github.com/${owner}/${repo}/releases/latest/download/${assetName}`;
254
- const response = await fetch(url);
255
- if (!response.ok) {
256
- throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
259
+ // Downloading the release asset happens outside SpawnService (which enters long-running mode
260
+ // for its own subprocesses by default), so bracket it here to get the same cooperative
261
+ // Ctrl-C handling during what can be the slowest step of the upgrade.
262
+ this.#shutdownService?.enterLongRunningMode();
263
+ let archiveData;
264
+ try {
265
+ const response = await fetch(url);
266
+ if (!response.ok) {
267
+ throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
268
+ }
269
+ archiveData = await response.arrayBuffer();
270
+ }
271
+ finally {
272
+ this.#shutdownService?.leaveLongRunningMode();
257
273
  }
258
274
  const tmpDir = await mkdtemp(join(tmpdir(), "upgrade-"));
259
275
  const archivePath = join(tmpDir, assetName);
260
- await Bun.write(archivePath, await response.arrayBuffer());
276
+ await Bun.write(archivePath, archiveData);
261
277
  const currentExecutable = process.execPath;
262
278
  if (os === SupportedOs.WINDOWS) {
263
279
  const extractResult = await this.#spawnService.spawn([
@@ -1 +1 @@
1
- {"version":3,"file":"UpgradeServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/UpgradeServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAK1E,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAKd,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB;IAKnE,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASpD,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CA6G1D"}
1
+ {"version":3,"file":"UpgradeServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/UpgradeServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAczE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAK1E,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAKd,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB;IAKnE,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASpD,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAgH1D"}
@@ -2,6 +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 { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
5
6
  import { Icon, PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
6
7
  import DefaultUpgradeService from "./DefaultUpgradeService.js";
7
8
  import { UpgradeSubCommand } from "./command/UpgradeSubCommand.js";
@@ -30,7 +31,10 @@ export default class UpgradeServiceProvider {
30
31
  const cliConfig = this.#cliConfig;
31
32
  if (context.doesServiceExist(SPAWN_SERVICE_ID)) {
32
33
  const spawnService = context.getServiceById(SPAWN_SERVICE_ID);
33
- upgradeService.setDependencies(spawnService);
34
+ const shutdownService = context.doesServiceExist(SHUTDOWN_SERVICE_ID)
35
+ ? context.getServiceById(SHUTDOWN_SERVICE_ID)
36
+ : undefined;
37
+ upgradeService.setDependencies(spawnService, shutdownService);
34
38
  }
35
39
  else {
36
40
  logger.debug(() => "SpawnService not available, upgrade install methods will be unavailable");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowscripter/dynamic-cli-framework",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
5
5
  "keywords": [
6
6
  "bun",
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import type {
6
6
  CLIConfig,
7
+ ShutdownService,
7
8
  SpawnService,
8
9
  UpgradeCheckResult,
9
10
  UpgradeResult,
@@ -20,6 +21,10 @@ import getLogger from "../../util/logger.ts";
20
21
 
21
22
  const logger = getLogger("DefaultUpgradeService");
22
23
 
24
+ // 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
+
23
28
  const OS_LABELS: Record<SupportedOs, string> = {
24
29
  [SupportedOs.LINUX]: "Linux",
25
30
  [SupportedOs.MACOS]: "MacOS",
@@ -28,6 +33,7 @@ const OS_LABELS: Record<SupportedOs, string> = {
28
33
 
29
34
  export default class DefaultUpgradeService implements UpgradeService {
30
35
  #spawnService: SpawnService | undefined;
36
+ #shutdownService: ShutdownService | undefined;
31
37
  readonly #config: UpgradeLocationsConfig;
32
38
  readonly #cliConfig: CLIConfig;
33
39
 
@@ -36,8 +42,9 @@ export default class DefaultUpgradeService implements UpgradeService {
36
42
  this.#cliConfig = cliConfig;
37
43
  }
38
44
 
39
- public setDependencies(spawnService: SpawnService): void {
45
+ public setDependencies(spawnService: SpawnService, shutdownService?: ShutdownService): void {
40
46
  this.#spawnService = spawnService;
47
+ this.#shutdownService = shutdownService;
41
48
  }
42
49
 
43
50
  public detectOs(): SupportedOs | undefined {
@@ -210,7 +217,10 @@ export default class DefaultUpgradeService implements UpgradeService {
210
217
  }
211
218
  const { owner, repo } = this.#config.githubRelease;
212
219
  try {
213
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
220
+ const response = await fetch(
221
+ `https://api.github.com/repos/${owner}/${repo}/releases/latest`,
222
+ { signal: AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS) },
223
+ );
214
224
  if (!response.ok) {
215
225
  return undefined;
216
226
  }
@@ -234,6 +244,7 @@ export default class DefaultUpgradeService implements UpgradeService {
234
244
  try {
235
245
  const response = await fetch(
236
246
  `https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`,
247
+ { signal: AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS) },
237
248
  );
238
249
  if (!response.ok) {
239
250
  return undefined;
@@ -316,14 +327,24 @@ export default class DefaultUpgradeService implements UpgradeService {
316
327
  const assetName = assetPattern.replace("{os}", OS_LABELS[os]).replace("{arch}", archLabel);
317
328
  const url = `https://github.com/${owner}/${repo}/releases/latest/download/${assetName}`;
318
329
 
319
- const response = await fetch(url);
320
- if (!response.ok) {
321
- throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
330
+ // Downloading the release asset happens outside SpawnService (which enters long-running mode
331
+ // for its own subprocesses by default), so bracket it here to get the same cooperative
332
+ // Ctrl-C handling during what can be the slowest step of the upgrade.
333
+ this.#shutdownService?.enterLongRunningMode();
334
+ let archiveData: ArrayBuffer;
335
+ try {
336
+ const response = await fetch(url);
337
+ if (!response.ok) {
338
+ throw new Error(`Failed to download release asset '${assetName}': HTTP ${response.status}`);
339
+ }
340
+ archiveData = await response.arrayBuffer();
341
+ } finally {
342
+ this.#shutdownService?.leaveLongRunningMode();
322
343
  }
323
344
 
324
345
  const tmpDir = await mkdtemp(join(tmpdir(), "upgrade-"));
325
346
  const archivePath = join(tmpDir, assetName);
326
- await Bun.write(archivePath, await response.arrayBuffer());
347
+ await Bun.write(archivePath, archiveData);
327
348
 
328
349
  const currentExecutable = process.execPath;
329
350
 
@@ -8,6 +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 { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
12
+ import type { ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
11
13
  import { Icon, PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
12
14
  import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
13
15
  import DefaultUpgradeService from "./DefaultUpgradeService.ts";
@@ -44,7 +46,10 @@ export default class UpgradeServiceProvider implements ServiceProvider {
44
46
 
45
47
  if (context.doesServiceExist(SPAWN_SERVICE_ID)) {
46
48
  const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
47
- upgradeService.setDependencies(spawnService);
49
+ const shutdownService = context.doesServiceExist(SHUTDOWN_SERVICE_ID)
50
+ ? (context.getServiceById(SHUTDOWN_SERVICE_ID) as ShutdownService)
51
+ : undefined;
52
+ upgradeService.setDependencies(spawnService, shutdownService);
48
53
  } else {
49
54
  logger.debug(() => "SpawnService not available, upgrade install methods will be unavailable");
50
55
  }