@flowscripter/dynamic-cli-framework 5.3.6 → 5.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"PluginAddSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/PluginAddSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AASxE,qBAAa,mBAAoB,YAAW,UAAU;IACpD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,WAAW,6BAA6B;IACjD,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,OAAO,UAAM;IACtB,QAAQ,CAAC,WAAW;;;;QAMlB;IAEI,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ErE;CACF"}
1
+ {"version":3,"file":"PluginAddSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/PluginAddSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AASxE,qBAAa,mBAAoB,YAAW,UAAU;IACpD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,WAAW,6BAA6B;IACjD,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,OAAO,UAAM;IACtB,QAAQ,CAAC,WAAW;;;;QAMlB;IAEI,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFrE;CACF"}
@@ -20,7 +20,7 @@ export class PluginAddSubCommand {
20
20
  const pluginService = context.getServiceById(PLUGIN_SERVICE_ID);
21
21
  const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"]);
22
22
  const searchLabel = version ? `${pluginId}@${version}` : pluginId;
23
- await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
23
+ await printerService.showSpinner(`Searching for plugin: ${searchLabel}`);
24
24
  let descriptor;
25
25
  for await (const d of pluginService.search({ text: pluginId })) {
26
26
  if (getPluginId(d) === pluginId || d.pluginId === pluginId) {
@@ -28,6 +28,7 @@ export class PluginAddSubCommand {
28
28
  break;
29
29
  }
30
30
  }
31
+ await printerService.hideSpinner();
31
32
  if (descriptor && version) {
32
33
  // search only ever returns the latest version - substitute the explicitly requested
33
34
  // version so install() is asked for the correct one.
@@ -62,7 +63,7 @@ export class PluginAddSubCommand {
62
63
  ? `Version ${versionToCheck} of plugin ${descriptor.pluginId} was not found in the configured plugin registry`
63
64
  : `Plugin ${descriptor.pluginId} was not found in the configured plugin registry`);
64
65
  }
65
- await printerService.info(`Installing ${installLabel}...\n`, Icon.INFORMATION);
66
+ await printerService.showSpinner(`Installing ${installLabel}...`);
66
67
  await pluginService.install(descriptor);
67
68
  // Look up the actually-installed version rather than trusting `descriptor.version`: when no
68
69
  // version was requested and the plugin wasn't found via search (direct-install fallback),
@@ -74,6 +75,7 @@ export class PluginAddSubCommand {
74
75
  break;
75
76
  }
76
77
  }
78
+ await printerService.hideSpinner();
77
79
  await printerService.print(`Plugin ${descriptor.pluginId}@${installedVersion} installed.\n`, Icon.SUCCESS);
78
80
  }
79
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PluginRemoveSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/PluginRemoveSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAMxE,qBAAa,sBAAuB,YAAW,UAAU;IACvD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,WAAW,uCAAuC;IAC3D,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,OAAO,UAAM;IACtB,QAAQ,CAAC,WAAW;;;;QAMlB;IAEI,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrE;CACF"}
1
+ {"version":3,"file":"PluginRemoveSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/PluginRemoveSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAMxE,qBAAa,sBAAuB,YAAW,UAAU;IACvD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,WAAW,uCAAuC;IAC3D,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,OAAO,UAAM;IACtB,QAAQ,CAAC,WAAW;;;;QAMlB;IAEI,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASrE;CACF"}
@@ -17,8 +17,9 @@ export class PluginRemoveSubCommand {
17
17
  const printerService = context.getServiceById(PRINTER_SERVICE_ID);
18
18
  const pluginService = context.getServiceById(PLUGIN_SERVICE_ID);
19
19
  const pluginId = argumentValues["pluginId"];
20
- await printerService.info(`Removing plugin: ${pluginId}...\n`, Icon.INFORMATION);
20
+ await printerService.showSpinner(`Removing plugin: ${pluginId}...`);
21
21
  await pluginService.uninstall(pluginId);
22
+ await printerService.hideSpinner();
22
23
  await printerService.print(`Plugin ${pluginId} removed.\n`, Icon.SUCCESS);
23
24
  }
24
25
  }
@@ -1,4 +1,4 @@
1
- import type { CLIConfig, FetchService, SpawnService, UpgradeCheckResult, UpgradeResult } from "@flowscripter/dynamic-cli-framework-api";
1
+ import type { CLIConfig, FetchService, PrinterService, 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 declare const VERSION_CHECK_TIMEOUT_MS = 250;
@@ -6,7 +6,7 @@ export declare function describeUpgradeCheckResult(result: UpgradeCheckResult):
6
6
  export default class DefaultUpgradeService implements UpgradeService {
7
7
  #private;
8
8
  constructor(config: UpgradeLocationsConfig, cliConfig: CLIConfig);
9
- setDependencies(spawnService: SpawnService | undefined, fetchService: FetchService | undefined): void;
9
+ setDependencies(spawnService: SpawnService | undefined, fetchService: FetchService | undefined, printerService: PrinterService | undefined): void;
10
10
  getUpgradeCheckResult(waitForResult?: boolean): Promise<UpgradeCheckResult>;
11
11
  detectOs(): SupportedOs | undefined;
12
12
  detectArch(): SupportedArch | 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,EAEZ,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;AAS1E,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAY5C,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAI7E;AAQD,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;IAOlE,YAAmB,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS,EAGtE;IAEM,eAAe,CACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,IAAI,CAGN;IAEM,qBAAqB,CAAC,aAAa,UAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0B/E;IAEM,QAAQ,IAAI,WAAW,GAAG,SAAS,CAWzC;IAEM,UAAU,IAAI,aAAa,GAAG,SAAS,CAS7C;IAEY,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAcpF;IAEY,eAAe,CAC1B,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAsC7B;IAEY,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC,CA+CxB;CAuRF"}
1
+ {"version":3,"file":"DefaultUpgradeService.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/DefaultUpgradeService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EAEd,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;AAS1E,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAY5C,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAI7E;AAQD,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;IAQlE,YAAmB,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS,EAGtE;IAEM,eAAe,CACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,cAAc,EAAE,cAAc,GAAG,SAAS,GACzC,IAAI,CAIN;IAoCM,qBAAqB,CAAC,aAAa,UAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0B/E;IAEM,QAAQ,IAAI,WAAW,GAAG,SAAS,CAWzC;IAEM,UAAU,IAAI,aAAa,GAAG,SAAS,CAS7C;IAEY,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAcpF;IAEY,eAAe,CAC1B,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAsC7B;IAEY,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC,CAsDxB;CAiRF"}
@@ -29,6 +29,7 @@ const OS_LABELS = {
29
29
  export default class DefaultUpgradeService {
30
30
  #spawnService;
31
31
  #fetchService;
32
+ #printerService;
32
33
  #upgradeCheckPromise;
33
34
  #config;
34
35
  #cliConfig;
@@ -36,9 +37,40 @@ export default class DefaultUpgradeService {
36
37
  this.#config = config;
37
38
  this.#cliConfig = cliConfig;
38
39
  }
39
- setDependencies(spawnService, fetchService) {
40
+ setDependencies(spawnService, fetchService, printerService) {
40
41
  this.#spawnService = spawnService;
41
42
  this.#fetchService = fetchService;
43
+ this.#printerService = printerService;
44
+ }
45
+ // Mirrors SpawnInterfaceAdapter's plugin:add/plugin:remove pattern: wrap a spawned command's
46
+ // output in a quoted, marked block that's cleared on success (so a clean install stays quiet)
47
+ // but left on screen on failure (so the diagnostic output remains visible).
48
+ async #spawnQuoted(command) {
49
+ const spawnService = this.#spawnService;
50
+ const printerService = this.#printerService;
51
+ if (!printerService) {
52
+ return spawnService.spawn(command, { mode: "ignore" });
53
+ }
54
+ printerService.startQuote();
55
+ printerService.startMark();
56
+ // onOutput is synchronous and may be called concurrently for stdout/stderr lines, but
57
+ // printerService.info() is async and must not be invoked concurrently with itself - queue
58
+ // writes so they're applied one at a time, in call order.
59
+ let writeQueue = Promise.resolve();
60
+ const onOutput = (line) => {
61
+ writeQueue = writeQueue.then(() => printerService.info(`${line}\n`));
62
+ };
63
+ const result = await spawnService.spawn(command, { mode: "wrapped", onOutput });
64
+ await writeQueue;
65
+ printerService.endQuote();
66
+ printerService.endMark();
67
+ if (result.ok) {
68
+ await printerService.clearMarked();
69
+ }
70
+ else {
71
+ printerService.discardMark();
72
+ }
73
+ return result;
42
74
  }
43
75
  getUpgradeCheckResult(waitForResult = false) {
44
76
  if (!this.#upgradeCheckPromise) {
@@ -161,7 +193,13 @@ export default class DefaultUpgradeService {
161
193
  if (checkResult.installMethod === InstallMethod.GITHUB_RELEASE && !this.#fetchService) {
162
194
  return { ok: false, oldVersion, error: new Error("FetchService is not available") };
163
195
  }
196
+ if (this.#printerService) {
197
+ await this.#printerService.showSpinner(`Installing version ${checkResult.latestVersion}...`);
198
+ }
164
199
  try {
200
+ if (this.#printerService) {
201
+ await this.#printerService.hideSpinner();
202
+ }
165
203
  switch (checkResult.installMethod) {
166
204
  case InstallMethod.LINUX_SCRIPT:
167
205
  await this.#upgradeViaLinuxScript();
@@ -306,25 +344,21 @@ export default class DefaultUpgradeService {
306
344
  }
307
345
  async #upgradeViaLinuxScript() {
308
346
  const { scriptUrl } = this.#config.linuxScript;
309
- const result = await this.#spawnService.spawn(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`], {
310
- mode: "ignore",
311
- });
347
+ const result = await this.#spawnQuoted(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`]);
312
348
  if (!result.ok) {
313
349
  throw new Error(`Install script failed: ${describeSpawnFailure(result)}`);
314
350
  }
315
351
  }
316
352
  async #upgradeViaHomebrew() {
317
353
  const { tap, formula } = this.#config.homebrew;
318
- const result = await this.#spawnService.spawn(["brew", "upgrade", `${tap}/${formula}`], {
319
- mode: "ignore",
320
- });
354
+ const result = await this.#spawnQuoted(["brew", "upgrade", `${tap}/${formula}`]);
321
355
  if (!result.ok) {
322
356
  throw new Error(`brew upgrade failed: ${describeSpawnFailure(result)}`);
323
357
  }
324
358
  }
325
359
  async #upgradeViaWinget() {
326
360
  const { packageId } = this.#config.winget;
327
- const result = await this.#spawnService.spawn([
361
+ const result = await this.#spawnQuoted([
328
362
  "winget",
329
363
  "upgrade",
330
364
  "--id",
@@ -332,7 +366,7 @@ export default class DefaultUpgradeService {
332
366
  "--silent",
333
367
  "--accept-package-agreements",
334
368
  "--accept-source-agreements",
335
- ], { mode: "ignore" });
369
+ ]);
336
370
  if (!result.ok) {
337
371
  throw new Error(`winget upgrade failed: ${describeSpawnFailure(result)}`);
338
372
  }
@@ -356,11 +390,11 @@ export default class DefaultUpgradeService {
356
390
  await Bun.write(archivePath, archiveData);
357
391
  const currentExecutable = process.execPath;
358
392
  if (os === SupportedOs.WINDOWS) {
359
- const extractResult = await this.#spawnService.spawn([
393
+ const extractResult = await this.#spawnQuoted([
360
394
  "powershell",
361
395
  "-Command",
362
396
  `Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}' -Force`,
363
- ], { mode: "ignore" });
397
+ ]);
364
398
  if (!extractResult.ok) {
365
399
  throw new Error("Failed to extract release archive");
366
400
  }
@@ -370,23 +404,36 @@ export default class DefaultUpgradeService {
370
404
  // Windows won't let us delete the just-renamed-aside exe while this process still has it
371
405
  // open/mapped - that can only happen once we're no longer holding it, i.e. at the start of
372
406
  // the NEXT invocation, before we move today's running exe aside. Ignore failures: the file
373
- // may not exist, or may still be locked (e.g. another instance still running).
407
+ // may not exist, or may still be locked (e.g. another instance still running). Not quoted -
408
+ // this is expected to fail silently, there's nothing worth showing the user.
374
409
  await this.#spawnService.spawn(["cmd", "/c", "del", "/f", "/q", oldPath], {
375
410
  mode: "ignore",
376
411
  });
377
- const moveResult = await this.#spawnService.spawn(["cmd", "/c", "move", "/y", currentExecutable, oldPath], { mode: "ignore" });
412
+ const moveResult = await this.#spawnQuoted([
413
+ "cmd",
414
+ "/c",
415
+ "move",
416
+ "/y",
417
+ currentExecutable,
418
+ oldPath,
419
+ ]);
378
420
  if (!moveResult.ok) {
379
421
  throw new Error("Failed to move current executable aside");
380
422
  }
381
- const copyResult = await this.#spawnService.spawn(["cmd", "/c", "copy", "/y", extractedBinary, currentExecutable], { mode: "ignore" });
423
+ const copyResult = await this.#spawnQuoted([
424
+ "cmd",
425
+ "/c",
426
+ "copy",
427
+ "/y",
428
+ extractedBinary,
429
+ currentExecutable,
430
+ ]);
382
431
  if (!copyResult.ok) {
383
432
  throw new Error("Failed to copy new executable into place");
384
433
  }
385
434
  }
386
435
  else {
387
- const extractResult = await this.#spawnService.spawn(["unzip", "-o", archivePath, "-d", tmpDir], {
388
- mode: "ignore",
389
- });
436
+ const extractResult = await this.#spawnQuoted(["unzip", "-o", archivePath, "-d", tmpDir]);
390
437
  if (!extractResult.ok) {
391
438
  throw new Error("Failed to extract release archive");
392
439
  }
@@ -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;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;IAKjC,YAAmB,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAGzE;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAOhE;IAEY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFxD;CAoCF"}
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;IAKjC,YAAmB,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAGzE;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAOhE;IAEY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxD;CAoCF"}
@@ -41,7 +41,8 @@ export default class UpgradeServiceProvider {
41
41
  if (fetchService === undefined) {
42
42
  logger.debug(() => "FetchService not available, upgrade version checks will be unavailable");
43
43
  }
44
- upgradeService.setDependencies(spawnService, fetchService);
44
+ const printerService = context.getServiceById(PRINTER_SERVICE_ID);
45
+ upgradeService.setDependencies(spawnService, fetchService, printerService);
45
46
  void upgradeService.getUpgradeCheckResult();
46
47
  if (!context.doesServiceExist(PROMPTER_SERVICE_ID)) {
47
48
  logger.debug(() => "PrompterService not available, skipping auto-upgrade");
@@ -1 +1 @@
1
- {"version":3,"file":"UpgradeSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/upgrade/command/UpgradeSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,EAAE,KAAK,MAAM,EAAiB,MAAM,yCAAyC,CAAC;AASrF,OAAO,KAAK,qBAAqB,MAAM,6BAA6B,CAAC;AAErE,qBAAa,iBAAkB,YAAW,UAAU;;IAClD,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,6CAA6C;IACjE,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,WAAW,UAAM;IAE1B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAerC;IAIF,YAAmB,cAAc,EAAE,qBAAqB,EAEvD;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD5E;CACF"}
1
+ {"version":3,"file":"UpgradeSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/upgrade/command/UpgradeSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,EAAE,KAAK,MAAM,EAAiB,MAAM,yCAAyC,CAAC;AASrF,OAAO,KAAK,qBAAqB,MAAM,6BAA6B,CAAC;AAErE,qBAAa,iBAAkB,YAAW,UAAU;;IAClD,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,6CAA6C;IACjE,QAAQ,CAAC,mBAAmB,SAAS;IACrC,QAAQ,CAAC,WAAW,UAAM;IAE1B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAerC;IAIF,YAAmB,cAAc,EAAE,qBAAqB,EAEvD;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4D5E;CACF"}
@@ -28,12 +28,15 @@ export class UpgradeSubCommand {
28
28
  async execute(context, argumentValues) {
29
29
  const printerService = context.getServiceById(PRINTER_SERVICE_ID);
30
30
  const cliName = context.cliConfig.name;
31
+ const currentVersion = context.cliConfig.version;
31
32
  const os = argumentValues.os;
32
33
  const arch = undefined;
33
34
  const installMethod = argumentValues["install-method"];
35
+ await printerService.showSpinner(`Looking for version newer than ${currentVersion}`);
34
36
  const checkResult = os === undefined && installMethod === undefined
35
37
  ? await this.#upgradeService.getUpgradeCheckResult(true)
36
38
  : await this.#upgradeService.checkForUpgrade(os, arch, installMethod);
39
+ await printerService.hideSpinner();
37
40
  if (checkResult.status === "unsupported") {
38
41
  await printerService.error(`No upgrade location is configured for the detected or requested platform.\n`, Icon.FAILURE);
39
42
  return;
@@ -48,7 +51,7 @@ export class UpgradeSubCommand {
48
51
  return;
49
52
  }
50
53
  if (!checkResult.updateAvailable) {
51
- await printerService.print(`${cliName} is already up to date (${checkResult.currentVersion}).\n`);
54
+ await printerService.print(`${cliName} is already up to date: ${checkResult.currentVersion}\n`, Icon.INFORMATION);
52
55
  return;
53
56
  }
54
57
  const upgradeResult = await this.#upgradeService.upgrade(os, arch, installMethod);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowscripter/dynamic-cli-framework",
3
- "version": "5.3.6",
3
+ "version": "5.4.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",
@@ -29,7 +29,7 @@ export class PluginAddSubCommand implements SubCommand {
29
29
 
30
30
  const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"] as string);
31
31
  const searchLabel = version ? `${pluginId}@${version}` : pluginId;
32
- await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
32
+ await printerService.showSpinner(`Searching for plugin: ${searchLabel}`);
33
33
 
34
34
  let descriptor: VersionedPluginDescriptor | undefined;
35
35
  for await (const d of pluginService.search({ text: pluginId })) {
@@ -38,6 +38,7 @@ export class PluginAddSubCommand implements SubCommand {
38
38
  break;
39
39
  }
40
40
  }
41
+ await printerService.hideSpinner();
41
42
 
42
43
  if (descriptor && version) {
43
44
  // search only ever returns the latest version - substitute the explicitly requested
@@ -83,7 +84,7 @@ export class PluginAddSubCommand implements SubCommand {
83
84
  );
84
85
  }
85
86
 
86
- await printerService.info(`Installing ${installLabel}...\n`, Icon.INFORMATION);
87
+ await printerService.showSpinner(`Installing ${installLabel}...`);
87
88
  await pluginService.install(descriptor);
88
89
 
89
90
  // Look up the actually-installed version rather than trusting `descriptor.version`: when no
@@ -96,6 +97,7 @@ export class PluginAddSubCommand implements SubCommand {
96
97
  break;
97
98
  }
98
99
  }
100
+ await printerService.hideSpinner();
99
101
 
100
102
  await printerService.print(
101
103
  `Plugin ${descriptor.pluginId}@${installedVersion} installed.\n`,
@@ -25,8 +25,9 @@ export class PluginRemoveSubCommand implements SubCommand {
25
25
  const pluginService = context.getServiceById(PLUGIN_SERVICE_ID) as PluginService;
26
26
 
27
27
  const pluginId = argumentValues["pluginId"] as string;
28
- await printerService.info(`Removing plugin: ${pluginId}...\n`, Icon.INFORMATION);
28
+ await printerService.showSpinner(`Removing plugin: ${pluginId}...`);
29
29
  await pluginService.uninstall(pluginId);
30
+ await printerService.hideSpinner();
30
31
  await printerService.print(`Plugin ${pluginId} removed.\n`, Icon.SUCCESS);
31
32
  }
32
33
  }
@@ -5,6 +5,7 @@ import { dirname, join } from "node:path";
5
5
  import type {
6
6
  CLIConfig,
7
7
  FetchService,
8
+ PrinterService,
8
9
  SpawnResult,
9
10
  SpawnService,
10
11
  UpgradeCheckResult,
@@ -53,6 +54,7 @@ const OS_LABELS: Record<SupportedOs, string> = {
53
54
  export default class DefaultUpgradeService implements UpgradeService {
54
55
  #spawnService: SpawnService | undefined;
55
56
  #fetchService: FetchService | undefined;
57
+ #printerService: PrinterService | undefined;
56
58
  #upgradeCheckPromise: Promise<UpgradeCheckResult> | undefined;
57
59
  readonly #config: UpgradeLocationsConfig;
58
60
  readonly #cliConfig: CLIConfig;
@@ -65,9 +67,45 @@ export default class DefaultUpgradeService implements UpgradeService {
65
67
  public setDependencies(
66
68
  spawnService: SpawnService | undefined,
67
69
  fetchService: FetchService | undefined,
70
+ printerService: PrinterService | undefined,
68
71
  ): void {
69
72
  this.#spawnService = spawnService;
70
73
  this.#fetchService = fetchService;
74
+ this.#printerService = printerService;
75
+ }
76
+
77
+ // Mirrors SpawnInterfaceAdapter's plugin:add/plugin:remove pattern: wrap a spawned command's
78
+ // output in a quoted, marked block that's cleared on success (so a clean install stays quiet)
79
+ // but left on screen on failure (so the diagnostic output remains visible).
80
+ async #spawnQuoted(command: ReadonlyArray<string>): Promise<SpawnResult> {
81
+ const spawnService = this.#spawnService!;
82
+ const printerService = this.#printerService;
83
+ if (!printerService) {
84
+ return spawnService.spawn(command, { mode: "ignore" });
85
+ }
86
+
87
+ printerService.startQuote();
88
+ printerService.startMark();
89
+
90
+ // onOutput is synchronous and may be called concurrently for stdout/stderr lines, but
91
+ // printerService.info() is async and must not be invoked concurrently with itself - queue
92
+ // writes so they're applied one at a time, in call order.
93
+ let writeQueue: Promise<void> = Promise.resolve();
94
+ const onOutput = (line: string): void => {
95
+ writeQueue = writeQueue.then(() => printerService.info(`${line}\n`));
96
+ };
97
+
98
+ const result = await spawnService.spawn(command, { mode: "wrapped", onOutput });
99
+ await writeQueue;
100
+
101
+ printerService.endQuote();
102
+ printerService.endMark();
103
+ if (result.ok) {
104
+ await printerService.clearMarked();
105
+ } else {
106
+ printerService.discardMark();
107
+ }
108
+ return result;
71
109
  }
72
110
 
73
111
  public getUpgradeCheckResult(waitForResult = false): Promise<UpgradeCheckResult> {
@@ -214,7 +252,14 @@ export default class DefaultUpgradeService implements UpgradeService {
214
252
  return { ok: false, oldVersion, error: new Error("FetchService is not available") };
215
253
  }
216
254
 
255
+ if (this.#printerService) {
256
+ await this.#printerService.showSpinner(`Installing version ${checkResult.latestVersion}...`);
257
+ }
258
+
217
259
  try {
260
+ if (this.#printerService) {
261
+ await this.#printerService.hideSpinner();
262
+ }
218
263
  switch (checkResult.installMethod) {
219
264
  case InstallMethod.LINUX_SCRIPT:
220
265
  await this.#upgradeViaLinuxScript();
@@ -385,9 +430,7 @@ export default class DefaultUpgradeService implements UpgradeService {
385
430
 
386
431
  async #upgradeViaLinuxScript(): Promise<void> {
387
432
  const { scriptUrl } = this.#config.linuxScript!;
388
- const result = await this.#spawnService!.spawn(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`], {
389
- mode: "ignore",
390
- });
433
+ const result = await this.#spawnQuoted(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`]);
391
434
  if (!result.ok) {
392
435
  throw new Error(`Install script failed: ${describeSpawnFailure(result)}`);
393
436
  }
@@ -395,9 +438,7 @@ export default class DefaultUpgradeService implements UpgradeService {
395
438
 
396
439
  async #upgradeViaHomebrew(): Promise<void> {
397
440
  const { tap, formula } = this.#config.homebrew!;
398
- const result = await this.#spawnService!.spawn(["brew", "upgrade", `${tap}/${formula}`], {
399
- mode: "ignore",
400
- });
441
+ const result = await this.#spawnQuoted(["brew", "upgrade", `${tap}/${formula}`]);
401
442
  if (!result.ok) {
402
443
  throw new Error(`brew upgrade failed: ${describeSpawnFailure(result)}`);
403
444
  }
@@ -405,18 +446,15 @@ export default class DefaultUpgradeService implements UpgradeService {
405
446
 
406
447
  async #upgradeViaWinget(): Promise<void> {
407
448
  const { packageId } = this.#config.winget!;
408
- const result = await this.#spawnService!.spawn(
409
- [
410
- "winget",
411
- "upgrade",
412
- "--id",
413
- packageId,
414
- "--silent",
415
- "--accept-package-agreements",
416
- "--accept-source-agreements",
417
- ],
418
- { mode: "ignore" },
419
- );
449
+ const result = await this.#spawnQuoted([
450
+ "winget",
451
+ "upgrade",
452
+ "--id",
453
+ packageId,
454
+ "--silent",
455
+ "--accept-package-agreements",
456
+ "--accept-source-agreements",
457
+ ]);
420
458
  if (!result.ok) {
421
459
  throw new Error(`winget upgrade failed: ${describeSpawnFailure(result)}`);
422
460
  }
@@ -446,14 +484,11 @@ export default class DefaultUpgradeService implements UpgradeService {
446
484
  const currentExecutable = process.execPath;
447
485
 
448
486
  if (os === SupportedOs.WINDOWS) {
449
- const extractResult = await this.#spawnService!.spawn(
450
- [
451
- "powershell",
452
- "-Command",
453
- `Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}' -Force`,
454
- ],
455
- { mode: "ignore" },
456
- );
487
+ const extractResult = await this.#spawnQuoted([
488
+ "powershell",
489
+ "-Command",
490
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}' -Force`,
491
+ ]);
457
492
  if (!extractResult.ok) {
458
493
  throw new Error("Failed to extract release archive");
459
494
  }
@@ -464,32 +499,36 @@ export default class DefaultUpgradeService implements UpgradeService {
464
499
  // Windows won't let us delete the just-renamed-aside exe while this process still has it
465
500
  // open/mapped - that can only happen once we're no longer holding it, i.e. at the start of
466
501
  // the NEXT invocation, before we move today's running exe aside. Ignore failures: the file
467
- // may not exist, or may still be locked (e.g. another instance still running).
502
+ // may not exist, or may still be locked (e.g. another instance still running). Not quoted -
503
+ // this is expected to fail silently, there's nothing worth showing the user.
468
504
  await this.#spawnService!.spawn(["cmd", "/c", "del", "/f", "/q", oldPath], {
469
505
  mode: "ignore",
470
506
  });
471
507
 
472
- const moveResult = await this.#spawnService!.spawn(
473
- ["cmd", "/c", "move", "/y", currentExecutable, oldPath],
474
- { mode: "ignore" },
475
- );
508
+ const moveResult = await this.#spawnQuoted([
509
+ "cmd",
510
+ "/c",
511
+ "move",
512
+ "/y",
513
+ currentExecutable,
514
+ oldPath,
515
+ ]);
476
516
  if (!moveResult.ok) {
477
517
  throw new Error("Failed to move current executable aside");
478
518
  }
479
- const copyResult = await this.#spawnService!.spawn(
480
- ["cmd", "/c", "copy", "/y", extractedBinary, currentExecutable],
481
- { mode: "ignore" },
482
- );
519
+ const copyResult = await this.#spawnQuoted([
520
+ "cmd",
521
+ "/c",
522
+ "copy",
523
+ "/y",
524
+ extractedBinary,
525
+ currentExecutable,
526
+ ]);
483
527
  if (!copyResult.ok) {
484
528
  throw new Error("Failed to copy new executable into place");
485
529
  }
486
530
  } else {
487
- const extractResult = await this.#spawnService!.spawn(
488
- ["unzip", "-o", archivePath, "-d", tmpDir],
489
- {
490
- mode: "ignore",
491
- },
492
- );
531
+ const extractResult = await this.#spawnQuoted(["unzip", "-o", archivePath, "-d", tmpDir]);
493
532
  if (!extractResult.ok) {
494
533
  throw new Error("Failed to extract release archive");
495
534
  }
@@ -56,7 +56,8 @@ export default class UpgradeServiceProvider implements ServiceProvider {
56
56
  if (fetchService === undefined) {
57
57
  logger.debug(() => "FetchService not available, upgrade version checks will be unavailable");
58
58
  }
59
- upgradeService.setDependencies(spawnService, fetchService);
59
+ const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
60
+ upgradeService.setDependencies(spawnService, fetchService, printerService);
60
61
 
61
62
  void upgradeService.getUpgradeCheckResult();
62
63
 
@@ -44,15 +44,20 @@ export class UpgradeSubCommand implements SubCommand {
44
44
  public async execute(context: Context, argumentValues: Values): Promise<void> {
45
45
  const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
46
46
  const cliName = context.cliConfig.name;
47
+ const currentVersion = context.cliConfig.version;
47
48
 
48
49
  const os = argumentValues.os as SupportedOs | undefined;
49
50
  const arch = undefined as SupportedArch | undefined;
50
51
  const installMethod = argumentValues["install-method"] as InstallMethod | undefined;
51
52
 
53
+ await printerService.showSpinner(`Looking for version newer than ${currentVersion}`);
54
+
52
55
  const checkResult =
53
56
  os === undefined && installMethod === undefined
54
57
  ? await this.#upgradeService.getUpgradeCheckResult(true)
55
58
  : await this.#upgradeService.checkForUpgrade(os, arch, installMethod);
59
+
60
+ await printerService.hideSpinner();
56
61
  if (checkResult.status === "unsupported") {
57
62
  await printerService.error(
58
63
  `No upgrade location is configured for the detected or requested platform.\n`,
@@ -77,7 +82,8 @@ export class UpgradeSubCommand implements SubCommand {
77
82
 
78
83
  if (!checkResult.updateAvailable) {
79
84
  await printerService.print(
80
- `${cliName} is already up to date (${checkResult.currentVersion}).\n`,
85
+ `${cliName} is already up to date: ${checkResult.currentVersion}\n`,
86
+ Icon.INFORMATION,
81
87
  );
82
88
  return;
83
89
  }