@flowscripter/dynamic-cli-framework 2.2.0 → 2.3.1

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.
Files changed (34) hide show
  1. package/dist/src/cli/BaseCLI.d.ts +2 -0
  2. package/dist/src/cli/BaseCLI.d.ts.map +1 -1
  3. package/dist/src/cli/BaseCLI.js +8 -0
  4. package/dist/src/cli/BaseCLIFeatureOptions.d.ts +3 -0
  5. package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
  6. package/dist/src/command/VersionCommand.d.ts.map +1 -1
  7. package/dist/src/command/VersionCommand.js +10 -1
  8. package/dist/src/service/banner/BannerServiceProvider.d.ts.map +1 -1
  9. package/dist/src/service/banner/BannerServiceProvider.js +8 -0
  10. package/dist/src/service/spawn/DefaultSpawnService.d.ts +6 -0
  11. package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -1
  12. package/dist/src/service/spawn/DefaultSpawnService.js +22 -1
  13. package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +14 -0
  14. package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -0
  15. package/dist/src/service/upgrade/DefaultUpgradeService.js +312 -0
  16. package/dist/src/service/upgrade/UpgradeLocationsConfig.d.ts +51 -0
  17. package/dist/src/service/upgrade/UpgradeLocationsConfig.d.ts.map +1 -0
  18. package/dist/src/service/upgrade/UpgradeLocationsConfig.js +0 -0
  19. package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts +13 -0
  20. package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -0
  21. package/dist/src/service/upgrade/UpgradeServiceProvider.js +120 -0
  22. package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts +16 -0
  23. package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts.map +1 -0
  24. package/dist/src/service/upgrade/command/UpgradeSubCommand.js +50 -0
  25. package/package.json +5 -3
  26. package/src/cli/BaseCLI.ts +9 -0
  27. package/src/cli/BaseCLIFeatureOptions.ts +4 -0
  28. package/src/command/VersionCommand.ts +13 -1
  29. package/src/service/banner/BannerServiceProvider.ts +13 -0
  30. package/src/service/spawn/DefaultSpawnService.ts +23 -1
  31. package/src/service/upgrade/DefaultUpgradeService.ts +379 -0
  32. package/src/service/upgrade/UpgradeLocationsConfig.ts +53 -0
  33. package/src/service/upgrade/UpgradeServiceProvider.ts +150 -0
  34. package/src/service/upgrade/command/UpgradeSubCommand.ts +85 -0
@@ -25,6 +25,8 @@ import type BaseCLIFeatureOptions from "./BaseCLIFeatureOptions.ts";
25
25
  *
26
26
  * * {@link ImagePrinterServiceProvider}
27
27
  * * {@link SpawnServiceProvider}
28
+ * * {@link CompletionServiceProvider}
29
+ * * {@link UpgradeServiceProvider}
28
30
  *
29
31
  * `keyReader` is optional. If omitted, or if the stderr {@link Terminal} is not a TTY, prompting is
30
32
  * unavailable: {@link PrompterServiceProvider} (and {@link ArgumentPrompterServiceProvider}, if
@@ -1 +1 @@
1
- {"version":3,"file":"BaseCLI.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yCAAyC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4BzE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,qBAAqB,MAAM,4BAA4B,CAAC;AAapE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,OAAO,OAAO,OAAQ,YAAW,GAAG;;gBAavC,SAAS,EAAE,SAAS,EACpB,oBAAoB,EAAE,cAAc,EACpC,oBAAoB,EAAE,cAAc,EACpC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,OAAO,EACtB,cAAc,EAAE,QAAQ,EACxB,cAAc,EAAE,QAAQ,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,qBAAqB;IA4DjC;;;;;;;OAOG;IACI,kBAAkB,CAAC,eAAe,EAAE,eAAe;IAI1D;;;;;;;;OAQG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO;IAU5B,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;CAuL3D"}
1
+ {"version":3,"file":"BaseCLI.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yCAAyC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4BzE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,qBAAqB,MAAM,4BAA4B,CAAC;AAcpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,OAAO,OAAO,OAAQ,YAAW,GAAG;;gBAavC,SAAS,EAAE,SAAS,EACpB,oBAAoB,EAAE,cAAc,EACpC,oBAAoB,EAAE,cAAc,EACpC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,OAAO,EACtB,cAAc,EAAE,QAAQ,EACxB,cAAc,EAAE,QAAQ,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,qBAAqB;IA8DjC;;;;;;;OAOG;IACI,kBAAkB,CAAC,eAAe,EAAE,eAAe;IAI1D;;;;;;;;OAQG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO;IAU5B,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;CA2L3D"}
@@ -26,6 +26,7 @@ import DefaultCompletionService from "../service/completion/DefaultCompletionSer
26
26
  import CompletionServiceProvider from "../service/completion/CompletionServiceProvider.js";
27
27
  import ImagePrinterServiceProvider from "../service/imagePrinter/ImagePrinterServiceProvider.js";
28
28
  import SpawnServiceProvider from "../service/spawn/SpawnServiceProvider.js";
29
+ import UpgradeServiceProvider from "../service/upgrade/UpgradeServiceProvider.js";
29
30
  const logger = getLogger("BaseCLI");
30
31
  /**
31
32
  * Base implementation of a {@link CLI}.
@@ -44,6 +45,8 @@ const logger = getLogger("BaseCLI");
44
45
  *
45
46
  * * {@link ImagePrinterServiceProvider}
46
47
  * * {@link SpawnServiceProvider}
48
+ * * {@link CompletionServiceProvider}
49
+ * * {@link UpgradeServiceProvider}
47
50
  *
48
51
  * `keyReader` is optional. If omitted, or if the stderr {@link Terminal} is not a TTY, prompting is
49
52
  * unavailable: {@link PrompterServiceProvider} (and {@link ArgumentPrompterServiceProvider}, if
@@ -92,6 +95,8 @@ export default class BaseCLI {
92
95
  completionServiceEnabled: false,
93
96
  imagePrinterServiceEnabled: false,
94
97
  spawnServiceEnabled: false,
98
+ upgradeServiceEnabled: false,
99
+ upgradeLocationsConfig: { supportedPlatforms: [] },
95
100
  validateAllCommands: false,
96
101
  promptingEnabled: true,
97
102
  ...options,
@@ -191,6 +196,9 @@ export default class BaseCLI {
191
196
  const completionService = new DefaultCompletionService();
192
197
  this.addServiceProvider(new CompletionServiceProvider(5, completionService, this.#commandRegistry));
193
198
  }
199
+ if (this.#options.upgradeServiceEnabled) {
200
+ this.addServiceProvider(new UpgradeServiceProvider(6, this.#options.upgradeLocationsConfig));
201
+ }
194
202
  const configurationServiceProvider = new ConfigurationServiceProvider(90, this.#options.envVarsSupportEnabled, this.#options.configFileSupportEnabled, this.#options.keyValueServiceEnabled, this.#options.secretServiceEnabled);
195
203
  this.addServiceProvider(configurationServiceProvider);
196
204
  for (const serviceProvider of this.#serviceProviderRegistry.getServiceProviders()) {
@@ -1,3 +1,4 @@
1
+ import type { UpgradeLocationsConfig } from "../service/upgrade/UpgradeLocationsConfig.ts";
1
2
  export default interface BaseCLIFeatureOptions {
2
3
  readonly configFileSupportEnabled?: boolean;
3
4
  readonly envVarsSupportEnabled?: boolean;
@@ -7,6 +8,8 @@ export default interface BaseCLIFeatureOptions {
7
8
  readonly completionServiceEnabled?: boolean;
8
9
  readonly imagePrinterServiceEnabled?: boolean;
9
10
  readonly spawnServiceEnabled?: boolean;
11
+ readonly upgradeServiceEnabled?: boolean;
12
+ readonly upgradeLocationsConfig?: UpgradeLocationsConfig;
10
13
  readonly validateAllCommands?: boolean;
11
14
  readonly promptingEnabled?: boolean;
12
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BaseCLIFeatureOptions.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLIFeatureOptions.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,WAAW,qBAAqB;IAC5C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IAClD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAC9C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC"}
1
+ {"version":3,"file":"BaseCLIFeatureOptions.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLIFeatureOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AAE3F,MAAM,CAAC,OAAO,WAAW,qBAAqB;IAC5C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IAClD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAC9C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IACzD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"VersionCommand.d.ts","sourceRoot":"","sources":["../../../src/command/VersionCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAI7E;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,YAAW,aAAa;IAC1D,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,8BAA8B;IAClD,QAAQ,CAAC,UAAU,OAAO;IAEb,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAItD"}
1
+ {"version":3,"file":"VersionCommand.d.ts","sourceRoot":"","sources":["../../../src/command/VersionCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAM7E;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,YAAW,aAAa;IAC1D,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,8BAA8B;IAClD,QAAQ,CAAC,UAAU,OAAO;IAEb,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CActD"}
@@ -1,4 +1,5 @@
1
1
  import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
2
+ import { UPGRADE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
2
3
  /**
3
4
  * Implementation of a {@link GlobalCommand} which outputs the version of the CLI.
4
5
  */
@@ -8,6 +9,14 @@ export default class VersionCommand {
8
9
  shortAlias = "v";
9
10
  async execute(context) {
10
11
  const printerService = context.getServiceById(PRINTER_SERVICE_ID);
11
- await printerService.print(`${context.cliConfig.version}\n`);
12
+ let line = context.cliConfig.version;
13
+ if (context.doesServiceExist(UPGRADE_SERVICE_ID)) {
14
+ const upgradeService = context.getServiceById(UPGRADE_SERVICE_ID);
15
+ const result = await upgradeService.checkForUpgrade().catch(() => undefined);
16
+ if (result?.updateAvailable) {
17
+ line += ` (${result.latestVersion} available, run '${context.cliConfig.name} upgrade')`;
18
+ }
19
+ }
20
+ await printerService.print(`${line}\n`);
12
21
  }
13
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BannerServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/banner/BannerServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAI5F,OAAO,KAAK,4BAA4B,MAAM,kDAAkD,CAAC;AAGjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,eAAO,MAAM,iBAAiB,uDAAuD,CAAC;AAEtF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,eAAe;;IAoBjE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAnB3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAqB;IAC/C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAGjC;;OAEG;IACH,WAAW,UAAQ;IAEnB;;;;;;OAMG;gBAED,eAAe,EAAE,MAAM,EACvB,4BAA4B,CAAC,EAAE,4BAA4B,EAC1C,QAAQ,GAAE,MAAmB;IAMzC,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAM5D,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAgCnD"}
1
+ {"version":3,"file":"BannerServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/banner/BannerServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAI5F,OAAO,KAAK,4BAA4B,MAAM,kDAAkD,CAAC;AAGjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,eAAO,MAAM,iBAAiB,uDAAuD,CAAC;AAEtF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,eAAe;;IAoBjE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAnB3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAqB;IAC/C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAGjC;;OAEG;IACH,WAAW,UAAQ;IAEnB;;;;;;OAMG;gBAED,eAAe,EAAE,MAAM,EACvB,4BAA4B,CAAC,EAAE,4BAA4B,EAC1C,QAAQ,GAAE,MAAmB;IAMzC,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAM5D,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CA2CnD"}
@@ -1,6 +1,7 @@
1
1
  import NoBannerCommand from "./command/NoBannerCommand.js";
2
2
  import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
3
3
  import { ASCII_BANNER_GENERATOR_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
4
+ import { UPGRADE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
4
5
  export const BANNER_SERVICE_ID = "@flowscripter/dynamic-cli-framework/banner-service";
5
6
  /**
6
7
  * Provides ascii banner functionality.
@@ -49,6 +50,13 @@ export default class BannerServiceProvider {
49
50
  if (cliConfig.version.length > 0) {
50
51
  await printerService.info(` ${printerService.secondary("version: " + cliConfig.version)}\n`);
51
52
  }
53
+ if (context.doesServiceExist(UPGRADE_SERVICE_ID)) {
54
+ const upgradeService = context.getServiceById(UPGRADE_SERVICE_ID);
55
+ const result = await upgradeService.checkForUpgrade().catch(() => undefined);
56
+ if (result?.updateAvailable) {
57
+ await printerService.info(` ${printerService.secondary(`(${result.latestVersion} available, run '${cliConfig.name} upgrade')`)}\n`);
58
+ }
59
+ }
52
60
  if (this.#configurationServiceProvider) {
53
61
  const configLocation = this.#configurationServiceProvider.configLocation;
54
62
  if (configLocation && configLocation.length > 0) {
@@ -1,4 +1,10 @@
1
1
  import type { PrinterService, ShutdownService, SpawnOptions, SpawnResult, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
2
+ /**
3
+ * On Windows, batch-file shims (e.g. `npm.cmd`, `bun.cmd`) cannot be launched directly via
4
+ * `CreateProcess` - they must be run through `cmd.exe /c`. Resolve the command through the shell
5
+ * in that case; leave it untouched on other platforms.
6
+ */
7
+ export declare function resolveForPlatform(command: ReadonlyArray<string>): string[];
2
8
  export default class DefaultSpawnService implements SpawnService {
3
9
  #private;
4
10
  setDependencies(printerService: PrinterService, shutdownService: ShutdownService): void;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultSpawnService.d.ts","sourceRoot":"","sources":["../../../../src/service/spawn/DefaultSpawnService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,WAAW,EACX,YAAY,EACb,MAAM,yCAAyC,CAAC;AAOjD,MAAM,CAAC,OAAO,OAAO,mBAAoB,YAAW,YAAY;;IAIvD,eAAe,CAAC,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,GAAG,IAAI;IAkCjF,KAAK,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,EAC9B,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC;CAgExB"}
1
+ {"version":3,"file":"DefaultSpawnService.d.ts","sourceRoot":"","sources":["../../../../src/service/spawn/DefaultSpawnService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,WAAW,EACX,YAAY,EACb,MAAM,yCAAyC,CAAC;AAOjD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAa3E;AAED,MAAM,CAAC,OAAO,OAAO,mBAAoB,YAAW,YAAY;;IAIvD,eAAe,CAAC,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,GAAG,IAAI;IAkCjF,KAAK,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,EAC9B,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC;CAiExB"}
@@ -1,6 +1,26 @@
1
+ import process from "node:process";
1
2
  import getLogger from "../../util/logger.js";
2
3
  const logger = getLogger("DefaultSpawnService");
3
4
  const SHUTDOWN_GRACE_PERIOD_MS = 5000;
5
+ /**
6
+ * On Windows, batch-file shims (e.g. `npm.cmd`, `bun.cmd`) cannot be launched directly via
7
+ * `CreateProcess` - they must be run through `cmd.exe /c`. Resolve the command through the shell
8
+ * in that case; leave it untouched on other platforms.
9
+ */
10
+ export function resolveForPlatform(command) {
11
+ if (process.platform !== "win32") {
12
+ return [...command];
13
+ }
14
+ const [bin, ...args] = command;
15
+ if (bin === undefined) {
16
+ return [...command];
17
+ }
18
+ const resolved = Bun.which(bin) ?? bin;
19
+ if (!/\.(cmd|bat)$/i.test(resolved)) {
20
+ return [resolved, ...args];
21
+ }
22
+ return ["cmd.exe", "/d", "/s", "/c", resolved, ...args];
23
+ }
4
24
  export default class DefaultSpawnService {
5
25
  #printerService;
6
26
  #shutdownService;
@@ -46,8 +66,9 @@ export default class DefaultSpawnService {
46
66
  }
47
67
  let proc;
48
68
  try {
49
- proc = Bun.spawn([...command], {
69
+ proc = Bun.spawn(resolveForPlatform(command), {
50
70
  cwd: options.cwd,
71
+ stdin: stdio === "wrapped" ? "ignore" : "inherit",
51
72
  stdout: stdio === "wrapped" ? "pipe" : "inherit",
52
73
  stderr: stdio === "wrapped" ? "pipe" : "inherit",
53
74
  });
@@ -0,0 +1,14 @@
1
+ import type { CLIConfig, SpawnService, UpgradeCheckResult, UpgradeResult } from "@flowscripter/dynamic-cli-framework-api";
2
+ import { InstallMethod, SupportedArch, SupportedOs, type UpgradeService } from "@flowscripter/dynamic-cli-framework-api";
3
+ import type { UpgradeLocationsConfig } from "./UpgradeLocationsConfig.ts";
4
+ export default class DefaultUpgradeService implements UpgradeService {
5
+ #private;
6
+ constructor(config: UpgradeLocationsConfig, cliConfig: CLIConfig);
7
+ setDependencies(spawnService: SpawnService): void;
8
+ detectOs(): SupportedOs | undefined;
9
+ detectArch(): SupportedArch | undefined;
10
+ detectInstallMethod(os: SupportedOs): Promise<InstallMethod | undefined>;
11
+ checkForUpgrade(osOverride?: SupportedOs, archOverride?: SupportedArch, installMethodOverride?: InstallMethod): Promise<UpgradeCheckResult | undefined>;
12
+ upgrade(osOverride?: SupportedOs, archOverride?: SupportedArch, installMethodOverride?: InstallMethod): Promise<UpgradeResult>;
13
+ }
14
+ //# sourceMappingURL=DefaultUpgradeService.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,312 @@
1
+ import process from "node:process";
2
+ import { mkdtemp } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { InstallMethod, SupportedArch, SupportedOs, } from "@flowscripter/dynamic-cli-framework-api";
6
+ import semver from "semver";
7
+ import getLogger from "../../util/logger.js";
8
+ const logger = getLogger("DefaultUpgradeService");
9
+ const OS_LABELS = {
10
+ [SupportedOs.LINUX]: "Linux",
11
+ [SupportedOs.MACOS]: "MacOS",
12
+ [SupportedOs.WINDOWS]: "Windows",
13
+ };
14
+ export default class DefaultUpgradeService {
15
+ #spawnService;
16
+ #config;
17
+ #cliConfig;
18
+ constructor(config, cliConfig) {
19
+ this.#config = config;
20
+ this.#cliConfig = cliConfig;
21
+ }
22
+ setDependencies(spawnService) {
23
+ this.#spawnService = spawnService;
24
+ }
25
+ detectOs() {
26
+ switch (process.platform) {
27
+ case "linux":
28
+ return SupportedOs.LINUX;
29
+ case "darwin":
30
+ return SupportedOs.MACOS;
31
+ case "win32":
32
+ return SupportedOs.WINDOWS;
33
+ default:
34
+ return undefined;
35
+ }
36
+ }
37
+ detectArch() {
38
+ switch (process.arch) {
39
+ case "x64":
40
+ return SupportedArch.X64;
41
+ case "arm64":
42
+ return SupportedArch.ARM64;
43
+ default:
44
+ return undefined;
45
+ }
46
+ }
47
+ async detectInstallMethod(os) {
48
+ if (os === SupportedOs.MACOS && this.#config.homebrew && (await this.#isHomebrewInstalled())) {
49
+ return InstallMethod.HOMEBREW;
50
+ }
51
+ if (os === SupportedOs.WINDOWS && this.#config.winget && (await this.#isWingetInstalled())) {
52
+ return InstallMethod.WINGET;
53
+ }
54
+ if (os === SupportedOs.LINUX && this.#config.linuxScript && this.#isLinuxScriptInstall()) {
55
+ return InstallMethod.LINUX_SCRIPT;
56
+ }
57
+ if (this.#config.githubRelease) {
58
+ return InstallMethod.GITHUB_RELEASE;
59
+ }
60
+ return undefined;
61
+ }
62
+ async checkForUpgrade(osOverride, archOverride, installMethodOverride) {
63
+ const os = osOverride ?? this.detectOs();
64
+ const arch = archOverride ?? this.detectArch();
65
+ if (!os || !arch || !this.#isPlatformSupported(os, arch)) {
66
+ return undefined;
67
+ }
68
+ const installMethod = installMethodOverride ?? (await this.detectInstallMethod(os));
69
+ if (!installMethod) {
70
+ return undefined;
71
+ }
72
+ const latestVersion = await this.#getLatestVersion(installMethod);
73
+ if (!latestVersion) {
74
+ return undefined;
75
+ }
76
+ const currentVersion = this.#cliConfig.version;
77
+ const coercedCurrent = semver.coerce(currentVersion);
78
+ const coercedLatest = semver.coerce(latestVersion);
79
+ if (!coercedCurrent || !coercedLatest) {
80
+ logger.debug(() => `Unable to compare versions '${currentVersion}' and '${latestVersion}'`);
81
+ return undefined;
82
+ }
83
+ return {
84
+ currentVersion,
85
+ latestVersion,
86
+ updateAvailable: semver.gt(coercedLatest, coercedCurrent),
87
+ os,
88
+ arch,
89
+ installMethod,
90
+ };
91
+ }
92
+ async upgrade(osOverride, archOverride, installMethodOverride) {
93
+ const oldVersion = this.#cliConfig.version;
94
+ const checkResult = await this.checkForUpgrade(osOverride, archOverride, installMethodOverride);
95
+ if (!checkResult) {
96
+ return {
97
+ ok: false,
98
+ oldVersion,
99
+ error: new Error("No upgrade location configured for the detected or requested platform"),
100
+ };
101
+ }
102
+ if (!this.#spawnService) {
103
+ return { ok: false, oldVersion, error: new Error("SpawnService is not available") };
104
+ }
105
+ try {
106
+ switch (checkResult.installMethod) {
107
+ case InstallMethod.LINUX_SCRIPT:
108
+ await this.#upgradeViaLinuxScript();
109
+ break;
110
+ case InstallMethod.HOMEBREW:
111
+ await this.#upgradeViaHomebrew();
112
+ break;
113
+ case InstallMethod.WINGET:
114
+ await this.#upgradeViaWinget();
115
+ break;
116
+ case InstallMethod.GITHUB_RELEASE:
117
+ await this.#upgradeViaGithubRelease(checkResult.os, checkResult.arch);
118
+ break;
119
+ }
120
+ return { ok: true, oldVersion, newVersion: checkResult.latestVersion };
121
+ }
122
+ catch (error) {
123
+ return { ok: false, oldVersion, error: error };
124
+ }
125
+ }
126
+ #isPlatformSupported(os, arch) {
127
+ return this.#config.supportedPlatforms.some((platform) => platform.os === os && platform.arch === arch);
128
+ }
129
+ async #isHomebrewInstalled() {
130
+ if (!this.#spawnService || !this.#config.homebrew) {
131
+ return false;
132
+ }
133
+ const result = await this.#spawnService.spawn(["brew", "list", "--versions", this.#config.homebrew.formula], { stdio: "wrapped", longRunning: false });
134
+ return result.ok;
135
+ }
136
+ async #isWingetInstalled() {
137
+ if (!this.#spawnService || !this.#config.winget) {
138
+ return false;
139
+ }
140
+ const result = await this.#spawnService.spawn(["winget", "list", "--id", this.#config.winget.packageId], { stdio: "wrapped", longRunning: false });
141
+ return result.ok;
142
+ }
143
+ #isLinuxScriptInstall() {
144
+ return process.execPath.startsWith("/usr/local/bin/");
145
+ }
146
+ async #getLatestVersion(installMethod) {
147
+ switch (installMethod) {
148
+ case InstallMethod.GITHUB_RELEASE:
149
+ case InstallMethod.LINUX_SCRIPT:
150
+ return this.#getLatestGithubReleaseVersion();
151
+ case InstallMethod.HOMEBREW:
152
+ return this.#getLatestHomebrewVersion();
153
+ case InstallMethod.WINGET:
154
+ return this.#getLatestWingetVersion();
155
+ default:
156
+ return undefined;
157
+ }
158
+ }
159
+ async #getLatestGithubReleaseVersion() {
160
+ if (!this.#config.githubRelease) {
161
+ return undefined;
162
+ }
163
+ const { owner, repo } = this.#config.githubRelease;
164
+ try {
165
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
166
+ if (!response.ok) {
167
+ return undefined;
168
+ }
169
+ const data = (await response.json());
170
+ return data.tag_name?.replace(/^v/, "");
171
+ }
172
+ catch (error) {
173
+ logger.debug(() => `Failed to fetch latest GitHub release for ${owner}/${repo}: ${error}`);
174
+ return undefined;
175
+ }
176
+ }
177
+ async #getLatestHomebrewVersion() {
178
+ if (!this.#config.homebrew) {
179
+ return undefined;
180
+ }
181
+ const { tap, formula } = this.#config.homebrew;
182
+ const [tapOwner, tapName] = tap.split("/");
183
+ if (!tapOwner || !tapName) {
184
+ return undefined;
185
+ }
186
+ try {
187
+ const response = await fetch(`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`);
188
+ if (!response.ok) {
189
+ return undefined;
190
+ }
191
+ const text = await response.text();
192
+ return /version\s+"v?([^"]+)"/.exec(text)?.[1];
193
+ }
194
+ catch (error) {
195
+ logger.debug(() => `Failed to fetch homebrew formula for ${tap}/${formula}: ${error}`);
196
+ return undefined;
197
+ }
198
+ }
199
+ async #getLatestWingetVersion() {
200
+ if (!this.#spawnService || !this.#config.winget) {
201
+ return undefined;
202
+ }
203
+ const lines = [];
204
+ const result = await this.#spawnService.spawn(["winget", "show", "--id", this.#config.winget.packageId], {
205
+ stdio: "wrapped",
206
+ longRunning: false,
207
+ onOutput: (line) => lines.push(line),
208
+ });
209
+ if (!result.ok) {
210
+ return undefined;
211
+ }
212
+ for (const line of lines) {
213
+ const match = /Version:\s*(\S+)/.exec(line);
214
+ if (match?.[1]) {
215
+ return match[1];
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+ async #upgradeViaLinuxScript() {
221
+ const { scriptUrl } = this.#config.linuxScript;
222
+ const result = await this.#spawnService.spawn(["sh", "-c", `curl -fsSL ${scriptUrl} | sh`]);
223
+ if (!result.ok) {
224
+ throw new Error(`Install script failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`);
225
+ }
226
+ }
227
+ async #upgradeViaHomebrew() {
228
+ const { tap, formula } = this.#config.homebrew;
229
+ const result = await this.#spawnService.spawn(["brew", "upgrade", `${tap}/${formula}`]);
230
+ if (!result.ok) {
231
+ throw new Error(`brew upgrade failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`);
232
+ }
233
+ }
234
+ async #upgradeViaWinget() {
235
+ const { packageId } = this.#config.winget;
236
+ const result = await this.#spawnService.spawn([
237
+ "winget",
238
+ "upgrade",
239
+ "--id",
240
+ packageId,
241
+ "--silent",
242
+ "--accept-package-agreements",
243
+ "--accept-source-agreements",
244
+ ]);
245
+ if (!result.ok) {
246
+ throw new Error(`winget upgrade failed: ${result.error?.message ?? `exit code ${result.exitCode}`}`);
247
+ }
248
+ }
249
+ async #upgradeViaGithubRelease(os, arch) {
250
+ const { owner, repo, assetPattern } = this.#config.githubRelease;
251
+ const archLabel = os === SupportedOs.MACOS ? "aarch64" : arch === SupportedArch.X64 ? "x64" : "arm64";
252
+ const assetName = assetPattern.replace("{os}", OS_LABELS[os]).replace("{arch}", archLabel);
253
+ 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}`);
257
+ }
258
+ const tmpDir = await mkdtemp(join(tmpdir(), "upgrade-"));
259
+ const archivePath = join(tmpDir, assetName);
260
+ await Bun.write(archivePath, await response.arrayBuffer());
261
+ const currentExecutable = process.execPath;
262
+ if (os === SupportedOs.WINDOWS) {
263
+ const extractResult = await this.#spawnService.spawn([
264
+ "powershell",
265
+ "-Command",
266
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}' -Force`,
267
+ ]);
268
+ if (!extractResult.ok) {
269
+ throw new Error("Failed to extract release archive");
270
+ }
271
+ const extractedBinary = join(tmpDir, `${this.#cliConfig.name}.exe`);
272
+ const oldPath = `${currentExecutable}.old.exe`;
273
+ const moveResult = await this.#spawnService.spawn([
274
+ "cmd",
275
+ "/c",
276
+ "move",
277
+ "/y",
278
+ currentExecutable,
279
+ oldPath,
280
+ ]);
281
+ if (!moveResult.ok) {
282
+ throw new Error("Failed to move current executable aside");
283
+ }
284
+ const copyResult = await this.#spawnService.spawn([
285
+ "cmd",
286
+ "/c",
287
+ "copy",
288
+ "/y",
289
+ extractedBinary,
290
+ currentExecutable,
291
+ ]);
292
+ if (!copyResult.ok) {
293
+ throw new Error("Failed to copy new executable into place");
294
+ }
295
+ }
296
+ else {
297
+ const extractResult = await this.#spawnService.spawn([
298
+ "unzip",
299
+ "-o",
300
+ archivePath,
301
+ "-d",
302
+ tmpDir,
303
+ ]);
304
+ if (!extractResult.ok) {
305
+ throw new Error("Failed to extract release archive");
306
+ }
307
+ const extractedBinary = join(tmpDir, this.#cliConfig.name);
308
+ await Bun.write(currentExecutable, Bun.file(extractedBinary));
309
+ await this.#spawnService.spawn(["chmod", "+x", currentExecutable]);
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,51 @@
1
+ import type { SupportedArch, SupportedOs } from "@flowscripter/dynamic-cli-framework-api";
2
+ /**
3
+ * Location of a manual GitHub release asset, and how to determine the latest available version.
4
+ */
5
+ export interface GithubReleaseLocation {
6
+ readonly owner: string;
7
+ readonly repo: string;
8
+ /**
9
+ * Asset filename pattern for a release, e.g. `"example-cli_{os}_{arch}.zip"`.
10
+ * `{os}` is substituted with `Linux`, `MacOS` or `Windows`; `{arch}` with `x64`, `arm64` or
11
+ * `aarch64` (macOS).
12
+ */
13
+ readonly assetPattern: string;
14
+ }
15
+ /**
16
+ * Location of a Linux install script, e.g. as documented for `curl -fsSL <scriptUrl> | sh`.
17
+ */
18
+ export interface LinuxScriptLocation {
19
+ readonly scriptUrl: string;
20
+ }
21
+ /**
22
+ * Location of a Homebrew tap formula, e.g. `brew install <tap>/<formula>`.
23
+ */
24
+ export interface HomebrewLocation {
25
+ readonly tap: string;
26
+ readonly formula: string;
27
+ }
28
+ /**
29
+ * Identifier of a Winget package, e.g. `winget install <packageId>`.
30
+ */
31
+ export interface WingetLocation {
32
+ readonly packageId: string;
33
+ }
34
+ /**
35
+ * CLI-author supplied configuration describing which os/arch combinations a CLI supports
36
+ * upgrading, and where to find the latest release for each supported install method.
37
+ *
38
+ * An install method is only usable for an os/arch combination if both its config block is
39
+ * present here AND that combination appears in {@link supportedPlatforms}.
40
+ */
41
+ export interface UpgradeLocationsConfig {
42
+ readonly supportedPlatforms: ReadonlyArray<{
43
+ os: SupportedOs;
44
+ arch: SupportedArch;
45
+ }>;
46
+ readonly githubRelease?: GithubReleaseLocation;
47
+ readonly linuxScript?: LinuxScriptLocation;
48
+ readonly homebrew?: HomebrewLocation;
49
+ readonly winget?: WingetLocation;
50
+ }
51
+ //# sourceMappingURL=UpgradeLocationsConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpgradeLocationsConfig.d.ts","sourceRoot":"","sources":["../../../../src/service/upgrade/UpgradeLocationsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,kBAAkB,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IACrF,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC"}
@@ -0,0 +1,13 @@
1
+ import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
2
+ import type { Context } from "@flowscripter/dynamic-cli-framework-api";
3
+ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
4
+ import type { UpgradeLocationsConfig } from "./UpgradeLocationsConfig.ts";
5
+ export default class UpgradeServiceProvider implements ServiceProvider {
6
+ #private;
7
+ readonly serviceId: string;
8
+ readonly servicePriority: number;
9
+ constructor(servicePriority: number, config: UpgradeLocationsConfig);
10
+ getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo>;
11
+ initService(context: Context): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=UpgradeServiceProvider.d.ts.map
@@ -0,0 +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"}