@flowscripter/dynamic-cli-framework 5.2.2 → 5.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.
@@ -11,7 +11,7 @@ export class PluginAddSubCommand {
11
11
  positionals = [
12
12
  {
13
13
  name: "pluginId",
14
- description: "Plugin ID to install (e.g. @scope/name or @scope/name:version)",
14
+ description: "Plugin ID to install (e.g. @scope/name or @scope/name@version)",
15
15
  type: ValueTypeName.STRING,
16
16
  },
17
17
  ];
@@ -19,7 +19,7 @@ export class PluginAddSubCommand {
19
19
  const printerService = context.getServiceById(PRINTER_SERVICE_ID);
20
20
  const pluginService = context.getServiceById(PLUGIN_SERVICE_ID);
21
21
  const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"]);
22
- const searchLabel = version ? `${pluginId}:${version}` : pluginId;
22
+ const searchLabel = version ? `${pluginId}@${version}` : pluginId;
23
23
  await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
24
24
  let descriptor;
25
25
  for await (const d of pluginService.search({ text: pluginId })) {
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Splits a plugin specifier into a bare plugin ID and an optional version.
2
+ * Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
3
+ * `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
4
+ * npm/JS ecosystem, so it is not supported here).
3
5
  *
4
- * The specifier is split on the *last* `:` character, if present. Scoped npm package names
5
- * (e.g. `@scope/name`) never legitimately contain a literal `:`, so this split cannot collide
6
- * with the scope/slash structure of the plugin ID itself.
6
+ * A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
7
+ * remainder - if any - is the version separator. This correctly distinguishes the scope marker
8
+ * (always the first character) from a version-separating `@` occurring later.
7
9
  *
8
- * Everything after the last `:` is always treated as the requested version, including
10
+ * Everything after the separator is always treated as the requested version, including
9
11
  * non-semver tags such as `latest` or `next` - no validation is performed on the tail.
10
12
  *
11
- * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name:3.0.0` or `name:latest`.
13
+ * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
14
+ * `name@latest`.
12
15
  */
13
16
  export declare function parsePluginSpecifier(specifier: string): {
14
17
  pluginId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"parsePluginSpecifier.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/parsePluginSpecifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAS9F"}
1
+ {"version":3,"file":"parsePluginSpecifier.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/parsePluginSpecifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAY9F"}
@@ -1,22 +1,28 @@
1
1
  /**
2
- * Splits a plugin specifier into a bare plugin ID and an optional version.
2
+ * Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
3
+ * `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
4
+ * npm/JS ecosystem, so it is not supported here).
3
5
  *
4
- * The specifier is split on the *last* `:` character, if present. Scoped npm package names
5
- * (e.g. `@scope/name`) never legitimately contain a literal `:`, so this split cannot collide
6
- * with the scope/slash structure of the plugin ID itself.
6
+ * A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
7
+ * remainder - if any - is the version separator. This correctly distinguishes the scope marker
8
+ * (always the first character) from a version-separating `@` occurring later.
7
9
  *
8
- * Everything after the last `:` is always treated as the requested version, including
10
+ * Everything after the separator is always treated as the requested version, including
9
11
  * non-semver tags such as `latest` or `next` - no validation is performed on the tail.
10
12
  *
11
- * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name:3.0.0` or `name:latest`.
13
+ * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
14
+ * `name@latest`.
12
15
  */
13
16
  export function parsePluginSpecifier(specifier) {
14
- const index = specifier.lastIndexOf(":");
15
- if (index === -1) {
17
+ const isScoped = specifier.startsWith("@");
18
+ const scopeEnd = isScoped ? specifier.indexOf("/") : -1;
19
+ const searchFrom = isScoped && scopeEnd !== -1 ? scopeEnd + 1 : 0;
20
+ const atIndex = specifier.lastIndexOf("@");
21
+ if (atIndex === -1 || atIndex < searchFrom) {
16
22
  return { pluginId: specifier };
17
23
  }
18
24
  return {
19
- pluginId: specifier.slice(0, index),
20
- version: specifier.slice(index + 1),
25
+ pluginId: specifier.slice(0, atIndex),
26
+ version: specifier.slice(atIndex + 1),
21
27
  };
22
28
  }
@@ -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;AAM1E,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAkB5C,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;gBAO/C,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS;IAKhE,eAAe,CACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,IAAI;IAKA,qBAAqB,CAAC,aAAa,UAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BzE,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,CAAC;IAwCjB,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC;CAsU1B"}
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;AAkB5C,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;gBAO/C,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,SAAS;IAKhE,eAAe,CACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,IAAI;IAKA,qBAAqB,CAAC,aAAa,UAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA4BzE,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,CAAC;IAwCjB,OAAO,CAClB,UAAU,CAAC,EAAE,WAAW,EACxB,YAAY,CAAC,EAAE,aAAa,EAC5B,qBAAqB,CAAC,EAAE,aAAa,GACpC,OAAO,CAAC,aAAa,CAAC;CAsU1B"}
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { InstallMethod, SupportedArch, SupportedOs, } from "@flowscripter/dynamic-cli-framework-api";
6
6
  import semver from "semver";
7
+ import getLogger from "../../util/logger.js";
8
+ const logger = getLogger("DefaultUpgradeService");
7
9
  // checkForUpgrade() runs opportunistically on every CLI invocation (via BannerServiceProvider),
8
10
  // so that opportunistic call is raced against this timeout so it never stalls CLI startup.
9
11
  // A deliberate caller (waitForResult=true, e.g. the `upgrade` command) awaits checkForUpgrade()
@@ -35,6 +37,7 @@ export default class DefaultUpgradeService {
35
37
  }
36
38
  getUpgradeCheckResult(waitForResult = false) {
37
39
  if (!this.#upgradeCheckPromise) {
40
+ logger.debug(() => "Starting upgrade check");
38
41
  // Only cache a non-"failed" result. A "failed" result can come from a transient issue
39
42
  // (e.g. a network blip, or the opportunistic startup check racing past
40
43
  // VERSION_CHECK_TIMEOUT_MS before checkForUpgrade() itself resolves) - caching that would
@@ -42,6 +45,7 @@ export default class DefaultUpgradeService {
42
45
  // waiting via waitForResult=true) any chance of a fresh attempt for the rest of this
43
46
  // process's lifetime.
44
47
  this.#upgradeCheckPromise = this.checkForUpgrade().then((result) => {
48
+ logger.debug(() => `Upgrade check result: ${JSON.stringify(result)}`);
45
49
  if (result.status === "failed") {
46
50
  this.#upgradeCheckPromise = undefined;
47
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowscripter/dynamic-cli-framework",
3
- "version": "5.2.2",
3
+ "version": "5.3.1",
4
4
  "description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
5
5
  "keywords": [
6
6
  "bun",
@@ -18,7 +18,7 @@ export class PluginAddSubCommand implements SubCommand {
18
18
  readonly positionals = [
19
19
  {
20
20
  name: "pluginId",
21
- description: "Plugin ID to install (e.g. @scope/name or @scope/name:version)",
21
+ description: "Plugin ID to install (e.g. @scope/name or @scope/name@version)",
22
22
  type: ValueTypeName.STRING,
23
23
  },
24
24
  ];
@@ -28,7 +28,7 @@ export class PluginAddSubCommand implements SubCommand {
28
28
  const pluginService = context.getServiceById(PLUGIN_SERVICE_ID) as PluginService;
29
29
 
30
30
  const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"] as string);
31
- const searchLabel = version ? `${pluginId}:${version}` : pluginId;
31
+ const searchLabel = version ? `${pluginId}@${version}` : pluginId;
32
32
  await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
33
33
 
34
34
  let descriptor: VersionedPluginDescriptor | undefined;
@@ -1,22 +1,28 @@
1
1
  /**
2
- * Splits a plugin specifier into a bare plugin ID and an optional version.
2
+ * Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
3
+ * `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
4
+ * npm/JS ecosystem, so it is not supported here).
3
5
  *
4
- * The specifier is split on the *last* `:` character, if present. Scoped npm package names
5
- * (e.g. `@scope/name`) never legitimately contain a literal `:`, so this split cannot collide
6
- * with the scope/slash structure of the plugin ID itself.
6
+ * A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
7
+ * remainder - if any - is the version separator. This correctly distinguishes the scope marker
8
+ * (always the first character) from a version-separating `@` occurring later.
7
9
  *
8
- * Everything after the last `:` is always treated as the requested version, including
10
+ * Everything after the separator is always treated as the requested version, including
9
11
  * non-semver tags such as `latest` or `next` - no validation is performed on the tail.
10
12
  *
11
- * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name:3.0.0` or `name:latest`.
13
+ * @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
14
+ * `name@latest`.
12
15
  */
13
16
  export function parsePluginSpecifier(specifier: string): { pluginId: string; version?: string } {
14
- const index = specifier.lastIndexOf(":");
15
- if (index === -1) {
17
+ const isScoped = specifier.startsWith("@");
18
+ const scopeEnd = isScoped ? specifier.indexOf("/") : -1;
19
+ const searchFrom = isScoped && scopeEnd !== -1 ? scopeEnd + 1 : 0;
20
+ const atIndex = specifier.lastIndexOf("@");
21
+ if (atIndex === -1 || atIndex < searchFrom) {
16
22
  return { pluginId: specifier };
17
23
  }
18
24
  return {
19
- pluginId: specifier.slice(0, index),
20
- version: specifier.slice(index + 1),
25
+ pluginId: specifier.slice(0, atIndex),
26
+ version: specifier.slice(atIndex + 1),
21
27
  };
22
28
  }
@@ -18,6 +18,9 @@ import {
18
18
  } from "@flowscripter/dynamic-cli-framework-api";
19
19
  import semver from "semver";
20
20
  import type { UpgradeLocationsConfig } from "./UpgradeLocationsConfig.ts";
21
+ import getLogger from "../../util/logger.ts";
22
+
23
+ const logger = getLogger("DefaultUpgradeService");
21
24
 
22
25
  // checkForUpgrade() runs opportunistically on every CLI invocation (via BannerServiceProvider),
23
26
  // so that opportunistic call is raced against this timeout so it never stalls CLI startup.
@@ -63,6 +66,7 @@ export default class DefaultUpgradeService implements UpgradeService {
63
66
 
64
67
  public getUpgradeCheckResult(waitForResult = false): Promise<UpgradeCheckResult> {
65
68
  if (!this.#upgradeCheckPromise) {
69
+ logger.debug(() => "Starting upgrade check");
66
70
  // Only cache a non-"failed" result. A "failed" result can come from a transient issue
67
71
  // (e.g. a network blip, or the opportunistic startup check racing past
68
72
  // VERSION_CHECK_TIMEOUT_MS before checkForUpgrade() itself resolves) - caching that would
@@ -70,6 +74,7 @@ export default class DefaultUpgradeService implements UpgradeService {
70
74
  // waiting via waitForResult=true) any chance of a fresh attempt for the rest of this
71
75
  // process's lifetime.
72
76
  this.#upgradeCheckPromise = this.checkForUpgrade().then((result) => {
77
+ logger.debug(() => `Upgrade check result: ${JSON.stringify(result)}`);
73
78
  if (result.status === "failed") {
74
79
  this.#upgradeCheckPromise = undefined;
75
80
  }