@digigov/cli 2.2.4-platform-262.04-06-26-10-20 → 2.3.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,22 +1,24 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import { DigigovCommand } from '../../lib/command.js';
5
- import { logger } from '../../lib/logger.js';
6
- import { findPackageJson } from '../../lib/project-utils.cjs';
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { DigigovCommand } from "../../lib/command.js";
5
+ import { logger } from "../../lib/logger.js";
6
+ import { findPackageJson } from "../../lib/project-utils.cjs";
7
7
  import {
8
8
  getScopedDependencies,
9
9
  getInstalledVersion,
10
- } from '../../lib/scope-scan.js';
10
+ } from "../../lib/scope-scan.js";
11
11
 
12
12
  export function createDoctorCommand() {
13
- const cmd = new DigigovCommand('doctor');
13
+ const cmd = new DigigovCommand("doctor");
14
14
  cmd
15
- .description('Verify every installed @digigov package shares the same version')
15
+ .description(
16
+ "Verify every installed @digigov package shares the same version",
17
+ )
16
18
  .action(async () => {
17
19
  const pkgPath = findPackageJson();
18
20
  if (!pkgPath) {
19
- logger.warn('No package.json found in the current directory tree.');
21
+ logger.warn("No package.json found in the current directory tree.");
20
22
  process.exitCode = 1;
21
23
  return;
22
24
  }
@@ -25,7 +27,7 @@ export function createDoctorCommand() {
25
27
  const pkg = await fs.readJSON(pkgPath);
26
28
  const scoped = getScopedDependencies(pkg);
27
29
  if (scoped.length === 0) {
28
- logger.warn('No @digigov/* dependencies declared. Nothing to check.');
30
+ logger.warn("No @digigov/* dependencies declared. Nothing to check.");
29
31
  return;
30
32
  }
31
33
 
@@ -38,13 +40,13 @@ export function createDoctorCommand() {
38
40
  .map((r) => r.installed)
39
41
  .filter(
40
42
  /** @type {(v: string | null) => v is string} */ (
41
- (v) => typeof v === 'string'
43
+ (v) => typeof v === "string"
42
44
  ),
43
45
  );
44
46
 
45
47
  if (installedVersions.length === 0) {
46
48
  logger.warn(
47
- 'No @digigov packages are actually installed in node_modules. Run install first.',
49
+ "No @digigov packages are actually installed in node_modules. Run install first.",
48
50
  );
49
51
  process.exitCode = 1;
50
52
  return;
@@ -59,20 +61,20 @@ export function createDoctorCommand() {
59
61
  return;
60
62
  }
61
63
 
62
- logger.error('Version drift detected across @digigov packages:');
64
+ logger.error("Version drift detected across @digigov packages:");
63
65
  const longest = rows.reduce((n, r) => Math.max(n, r.name.length), 0);
64
66
  for (const r of rows) {
65
67
  const pad = r.name.padEnd(longest);
66
68
  const installed = r.installed
67
69
  ? chalk.red(r.installed)
68
- : chalk.dim('(not installed)');
70
+ : chalk.dim("(not installed)");
69
71
  console.log(
70
72
  ` ${pad} declared: ${chalk.dim(r.range)} installed: ${installed} ${chalk.dim(`(${r.kind})`)}`,
71
73
  );
72
74
  }
73
75
  console.log(
74
76
  chalk.yellow(
75
- '\nRun `digigov packages update` to realign your packages.',
77
+ "\nRun `digigov packages update` to realign your packages.",
76
78
  ),
77
79
  );
78
80
  process.exitCode = 1;
@@ -1,19 +1,18 @@
1
- import { execa } from 'execa';
2
- import * as p from '@clack/prompts';
3
- import ora from 'ora';
4
- import chalk from 'chalk';
5
- import { DigigovCommand } from '../../lib/command.js';
6
- import { logger } from '../../lib/logger.js';
7
- import { detectPackageManager, buildAddArgs } from '../../lib/pm-detect.js';
8
- import { fetchScopeManifest } from '../../lib/manifest.js';
9
- import { RegistryError } from '../../lib/registry.js';
1
+ import { execa } from "execa";
2
+ import * as p from "@clack/prompts";
3
+ import ora from "ora";
4
+ import { DigigovCommand } from "../../lib/command.js";
5
+ import { logger } from "../../lib/logger.js";
6
+ import { detectPackageManager, buildAddArgs } from "../../lib/pm-detect.js";
7
+ import { fetchScopeManifest } from "../../lib/manifest.js";
8
+ import { RegistryError } from "../../lib/registry.js";
10
9
 
11
10
  export function createInitCommand() {
12
- const cmd = new DigigovCommand('init');
11
+ const cmd = new DigigovCommand("init");
13
12
  cmd
14
- .description('Interactively install one or more @digigov packages')
15
- .option('--tag <tag>', 'Skip the tag prompt and use this dist-tag', '')
16
- .option('-y, --yes', 'Install every package without prompting', false)
13
+ .description("Interactively install one or more @digigov packages")
14
+ .option("--tag <tag>", "Skip the tag prompt and use this dist-tag", "")
15
+ .option("-y, --yes", "Install every package without prompting", false)
17
16
  .action(
18
17
  /**
19
18
  * @param {{ tag: string, yes: boolean }} opts
@@ -33,37 +32,30 @@ export function createInitCommand() {
33
32
  : await promptForPackages(manifest);
34
33
 
35
34
  if (!selected || selected.length === 0) {
36
- logger.warn('No packages selected. Nothing to install.');
35
+ logger.warn("No packages selected. Nothing to install.");
37
36
  return;
38
37
  }
39
38
 
40
- const tag =
41
- opts.tag ||
42
- (opts.yes
43
- ? 'latest'
44
- : await promptForTag());
39
+ const tag = opts.tag || (opts.yes ? "latest" : await promptForTag());
45
40
 
46
41
  if (!tag) {
47
- logger.warn('No version stream chosen. Aborting.');
42
+ logger.warn("No version stream chosen. Aborting.");
48
43
  return;
49
44
  }
50
45
 
51
46
  const specs = selected.map((name) => `${name}@${tag}`);
52
- const spinner = ora({
53
- text: `Installing ${selected.length} package(s) via ${pm}…`,
54
- }).start();
47
+
48
+ logger.log(`Installing ${selected.length} package(s) via ${pm}…`);
55
49
  try {
56
50
  await execa(pm, buildAddArgs(pm, specs), {
57
51
  cwd: root,
58
- stdio: 'inherit',
52
+ stdio: "inherit",
59
53
  });
60
- spinner.succeed(
61
- chalk.green(
62
- `Installed ${selected.length} @digigov package(s) at tag "${tag}".`,
63
- ),
54
+ logger.success(
55
+ `Installed ${selected.length} @digigov package(s) at tag "${tag}".`,
64
56
  );
65
57
  } catch (err) {
66
- spinner.fail('Install failed.');
58
+ logger.error("Install failed.");
67
59
  throw err;
68
60
  }
69
61
  },
@@ -72,11 +64,11 @@ export function createInitCommand() {
72
64
  }
73
65
 
74
66
  async function loadManifest() {
75
- const spinner = ora({ text: 'Fetching @digigov package catalog…' }).start();
67
+ const spinner = ora({ text: "Fetching @digigov package catalog…" }).start();
76
68
  try {
77
69
  const manifest = await fetchScopeManifest();
78
70
  if (manifest.length === 0) {
79
- spinner.warn('Registry returned no @digigov packages.');
71
+ spinner.warn("Registry returned no @digigov packages.");
80
72
  return null;
81
73
  }
82
74
  spinner.succeed(`Found ${manifest.length} @digigov package(s).`);
@@ -86,17 +78,25 @@ async function loadManifest() {
86
78
  spinner.fail(err.message);
87
79
  return null;
88
80
  }
89
- spinner.fail('Failed to fetch the package catalog.');
81
+ spinner.fail("Failed to fetch the package catalog.");
90
82
  throw err;
91
83
  }
92
84
  }
93
85
 
86
+ function checkCancel(result) {
87
+ if (p.isCancel(result)) {
88
+ p.cancel("Aborted.");
89
+ return null;
90
+ }
91
+ return result;
92
+ }
93
+
94
94
  /**
95
95
  * @param {{ name: string, description: string }[]} manifest
96
96
  */
97
97
  async function promptForPackages(manifest) {
98
98
  const result = await p.multiselect({
99
- message: 'Select packages to install',
99
+ message: "Select packages to install",
100
100
  options: manifest.map((m) => ({
101
101
  value: m.name,
102
102
  label: m.name,
@@ -104,25 +104,21 @@ async function promptForPackages(manifest) {
104
104
  })),
105
105
  required: false,
106
106
  });
107
- if (p.isCancel(result)) {
108
- p.cancel('Aborted.');
109
- return null;
110
- }
111
- return /** @type {string[]} */ (result);
107
+ const selection = checkCancel(result);
108
+ if (selection === null) return null;
109
+ return /** @type {string[]} */ (selection);
112
110
  }
113
111
 
114
112
  async function promptForTag() {
115
113
  const result = await p.select({
116
- message: 'Which release stream?',
114
+ message: "Which release stream?",
117
115
  options: [
118
- { value: 'latest', label: 'Stable (latest)' },
119
- { value: 'next', label: 'Pre-release (next)' },
116
+ { value: "latest", label: "Stable (latest)" },
117
+ { value: "next", label: "Pre-release (next)" },
120
118
  ],
121
- initialValue: 'latest',
119
+ initialValue: "latest",
122
120
  });
123
- if (p.isCancel(result)) {
124
- p.cancel('Aborted.');
125
- return null;
126
- }
127
- return /** @type {string} */ (result);
121
+ const selection = checkCancel(result);
122
+ if (selection === null) return null;
123
+ return /** @type {string} */ (selection);
128
124
  }
@@ -1,22 +1,34 @@
1
- import fs from 'fs-extra';
2
- import { execa } from 'execa';
3
- import * as p from '@clack/prompts';
4
- import ora from 'ora';
5
- import chalk from 'chalk';
6
- import { DigigovCommand } from '../../lib/command.js';
7
- import { logger } from '../../lib/logger.js';
8
- import { findPackageJson } from '../../lib/project-utils.cjs';
9
- import { detectPackageManager, buildInstallArgs } from '../../lib/pm-detect.js';
10
- import { getDistTags, RegistryError } from '../../lib/registry.js';
11
- import { getScopedDependencies } from '../../lib/scope-scan.js';
12
- import { VERSION_ORACLE_PACKAGE } from '../../lib/manifest.js';
1
+ import fs from "fs-extra";
2
+ import { execa } from "execa";
3
+ import * as p from "@clack/prompts";
4
+ import ora from "ora";
5
+ import chalk from "chalk";
6
+ import { DigigovCommand } from "../../lib/command.js";
7
+ import { logger } from "../../lib/logger.js";
8
+ import { findPackageJson } from "../../lib/project-utils.cjs";
9
+ import { detectPackageManager, buildInstallArgs } from "../../lib/pm-detect.js";
10
+ import {
11
+ getDistTags,
12
+ getPackageInfo,
13
+ RegistryError,
14
+ } from "../../lib/registry.js";
15
+ import { getScopedDependencies } from "../../lib/scope-scan.js";
16
+ import { VERSION_ORACLE_PACKAGE } from "../../lib/manifest.js";
13
17
 
14
18
  export function createUpdateCommand() {
15
- const cmd = new DigigovCommand('update');
19
+ const cmd = new DigigovCommand("update");
16
20
  cmd
17
- .description('Bulk-realign every installed @digigov package to one version')
18
- .option('--tag <tag>', 'Skip the prompt and use this dist-tag or version', '')
19
- .option('--dry-run', 'Print the planned changes without writing or installing', false)
21
+ .description("Bulk-realign every installed @digigov package to one version")
22
+ .option(
23
+ "--tag <tag>",
24
+ "Skip the prompt and use this dist-tag or version",
25
+ "",
26
+ )
27
+ .option(
28
+ "--dry-run",
29
+ "Print the planned changes without writing or installing",
30
+ false,
31
+ )
20
32
  .action(
21
33
  /**
22
34
  * @param {{ tag: string, dryRun: boolean }} opts
@@ -25,14 +37,14 @@ export function createUpdateCommand() {
25
37
  const { pm, root } = detectPackageManager();
26
38
  const pkgPath = findPackageJson(root);
27
39
  if (!pkgPath) {
28
- logger.warn('No package.json found in the project root.');
40
+ logger.warn("No package.json found in the project root.");
29
41
  return;
30
42
  }
31
43
  /** @type {Record<string, unknown>} */
32
44
  const pkg = await fs.readJSON(pkgPath);
33
45
  const scoped = getScopedDependencies(pkg);
34
46
  if (scoped.length === 0) {
35
- logger.warn('No @digigov/* dependencies found. Nothing to update.');
47
+ logger.warn("No @digigov/* dependencies found. Nothing to update.");
36
48
  return;
37
49
  }
38
50
 
@@ -61,26 +73,24 @@ export function createUpdateCommand() {
61
73
  printDiffTable(diff);
62
74
 
63
75
  if (opts.dryRun) {
64
- logger.info('Dry run — no files written.');
76
+ logger.info("Dry run — no files written.");
65
77
  return;
66
78
  }
67
79
 
68
80
  rewritePackageJson(pkg, diff, targetVersion);
69
81
  await fs.writeJSON(pkgPath, pkg, { spaces: 2 });
70
82
 
71
- const spinner = ora({ text: `Reinstalling via ${pm}…` }).start();
83
+ logger.log(`Reinstalling via ${pm}…`);
72
84
  try {
73
- await execa(pm, buildInstallArgs(pm), {
85
+ await execa(pm, buildInstallArgs(), {
74
86
  cwd: root,
75
- stdio: 'inherit',
87
+ stdio: "inherit",
76
88
  });
77
- spinner.succeed(
78
- chalk.green(
79
- `All @digigov packages now aligned on v${targetVersion}.`,
80
- ),
89
+ logger.success(
90
+ `All @digigov packages now aligned on v${targetVersion}.`,
81
91
  );
82
92
  } catch (err) {
83
- spinner.fail('Install failed.');
93
+ logger.error("Install failed.");
84
94
  throw err;
85
95
  }
86
96
  },
@@ -88,6 +98,22 @@ export function createUpdateCommand() {
88
98
  return cmd;
89
99
  }
90
100
 
101
+ function handleRegistryError(err) {
102
+ if (err instanceof RegistryError) {
103
+ logger.error(err.message);
104
+ return null;
105
+ }
106
+ throw err;
107
+ }
108
+
109
+ function checkCancel(result) {
110
+ if (p.isCancel(result)) {
111
+ p.cancel("Aborted.");
112
+ return null;
113
+ }
114
+ return result;
115
+ }
116
+
91
117
  async function resolveTagOrVersion(/** @type {string} */ input) {
92
118
  if (/^\d/.test(input)) return input;
93
119
  try {
@@ -95,54 +121,86 @@ async function resolveTagOrVersion(/** @type {string} */ input) {
95
121
  const v = tags[input];
96
122
  if (!v) {
97
123
  logger.error(
98
- `Unknown dist-tag "${input}". Known: ${Object.keys(tags).join(', ') || '<none>'}`,
124
+ `Unknown dist-tag "${input}". Known: ${Object.keys(tags).join(", ") || "<none>"}`,
99
125
  );
100
126
  return null;
101
127
  }
102
128
  return v;
103
129
  } catch (err) {
104
- if (err instanceof RegistryError) {
105
- logger.error(err.message);
106
- return null;
107
- }
108
- throw err;
130
+ return handleRegistryError(err);
109
131
  }
110
132
  }
111
133
 
134
+ const VERSION_LIMIT = 20;
135
+
112
136
  async function promptForTarget() {
113
- let tags;
137
+ let info;
138
+ const spinner = ora({
139
+ text: `Fetching ${VERSION_LIMIT} most recent versions from the registry…`,
140
+ }).start();
114
141
  try {
115
- tags = await getDistTags(VERSION_ORACLE_PACKAGE);
142
+ info = await getPackageInfo(VERSION_ORACLE_PACKAGE);
143
+ spinner.stop();
116
144
  } catch (err) {
117
- if (err instanceof RegistryError) {
118
- logger.error(err.message);
119
- return null;
120
- }
121
- throw err;
122
- }
123
- const options = [];
124
- if (tags['latest']) {
125
- options.push({
126
- value: tags['latest'],
127
- label: `v${tags['latest']} (Latest Release)`,
128
- });
145
+ spinner.fail("Failed to fetch versions.");
146
+ return handleRegistryError(err);
129
147
  }
130
- if (tags['next']) {
131
- options.push({
132
- value: tags['next'],
133
- label: `v${tags['next']} (Next Pre-release)`,
134
- });
135
- }
136
- if (options.length === 0) {
137
- logger.error('Registry returned no usable dist-tags.');
148
+ if (info.versions.length === 0) {
149
+ logger.error("Registry returned no versions for the oracle package.");
138
150
  return null;
139
151
  }
140
- const result = await p.select({ message: 'Which version target?', options });
141
- if (p.isCancel(result)) {
142
- p.cancel('Aborted.');
143
- return null;
152
+ const taggedLatest = info.distTags["latest"];
153
+ const taggedNext = info.distTags["next"];
154
+
155
+ const recent = info.versions.slice(0, VERSION_LIMIT);
156
+ const ordered = [...recent];
157
+ for (const tagged of [taggedLatest, taggedNext]) {
158
+ if (tagged && !ordered.includes(tagged)) ordered.push(tagged);
144
159
  }
145
- return /** @type {string} */ (result);
160
+
161
+ const options = ordered.map((version) => {
162
+ const hints = [];
163
+
164
+ if (version === taggedLatest) hints.push("latest");
165
+ if (version === taggedNext) hints.push("next");
166
+
167
+ const issue = extractIssueId(version);
168
+ if (issue) hints.push(issue);
169
+
170
+ return {
171
+ value: version,
172
+ label: `v${version}`,
173
+ hint: hints.join(", ") || undefined,
174
+ };
175
+ });
176
+
177
+ const initialValue = taggedLatest ?? options[0]?.value;
178
+ const result = await p.select({
179
+ message: `Pick a target version`,
180
+ options,
181
+ initialValue,
182
+ });
183
+ const selection = checkCancel(result);
184
+ if (selection === null) return null;
185
+ return /** @type {string} */ (selection);
186
+ }
187
+
188
+ /**
189
+ * Beachball pre-release tags ship with custom identifiers like
190
+ * `2.2.4-platform-262.04-06-26-10-20`: the first dot-segment of the
191
+ * pre-release tail carries the issue id (`platform-262`). Surface it as a
192
+ * hint so users can pick the right build at a glance.
193
+ *
194
+ * @param {string} v
195
+ */
196
+ function extractIssueId(v) {
197
+ const dashIdx = v.indexOf("-");
198
+ if (dashIdx === -1) return null;
199
+ const tail = v.slice(dashIdx + 1);
200
+ const firstSegment = tail.split(".")[0];
201
+ if (!firstSegment) return null;
202
+ if (/^\d+(\.\d+)*$/.test(firstSegment)) return null;
203
+ return firstSegment;
146
204
  }
147
205
 
148
206
  /**
@@ -152,9 +210,11 @@ function printDiffTable(diff) {
152
210
  const longest = diff.reduce((n, d) => Math.max(n, d.name.length), 0);
153
211
  for (const d of diff) {
154
212
  const pad = d.name.padEnd(longest);
155
- const arrow = d.changed ? chalk.yellow('') : chalk.dim('=');
213
+ const arrow = d.changed ? chalk.yellow("") : chalk.dim("=");
156
214
  const to = d.changed ? chalk.green(d.to) : chalk.dim(d.to);
157
- console.log(` ${pad} ${d.from} ${arrow} ${to} ${chalk.dim(`(${d.kind})`)}`);
215
+ console.log(
216
+ ` ${pad} ${d.from} ${arrow} ${to} ${chalk.dim(`(${d.kind})`)}`,
217
+ );
158
218
  }
159
219
  }
160
220
 
@@ -165,7 +225,9 @@ function printDiffTable(diff) {
165
225
  */
166
226
  function rewritePackageJson(pkg, diff, version) {
167
227
  for (const d of diff) {
168
- const block = /** @type {Record<string, string> | undefined} */ (pkg[d.kind]);
228
+ const block = /** @type {Record<string, string> | undefined} */ (
229
+ pkg[d.kind]
230
+ );
169
231
  if (block && d.name in block) {
170
232
  block[d.name] = version;
171
233
  }
package/lib/pm-detect.js CHANGED
@@ -47,7 +47,6 @@ export function detectPackageManager(startDir = process.cwd()) {
47
47
  export function buildAddArgs(pm, specs) {
48
48
  switch (pm) {
49
49
  case 'pnpm':
50
- return ['add', ...specs];
51
50
  case 'yarn':
52
51
  return ['add', ...specs];
53
52
  case 'npm':
@@ -59,16 +58,8 @@ export function buildAddArgs(pm, specs) {
59
58
  * Build the package manager argv for a plain reinstall after the local
60
59
  * `package.json` has already been rewritten.
61
60
  *
62
- * @param {PackageManager} pm
63
61
  * @returns {string[]}
64
62
  */
65
- export function buildInstallArgs(pm) {
66
- switch (pm) {
67
- case 'pnpm':
68
- return ['install'];
69
- case 'yarn':
70
- return ['install'];
71
- case 'npm':
72
- return ['install'];
73
- }
63
+ export function buildInstallArgs() {
64
+ return ['install'];
74
65
  }
package/lib/registry.js CHANGED
@@ -40,14 +40,15 @@ function encodeName(name) {
40
40
 
41
41
  /**
42
42
  * @param {string} url
43
+ * @param {Record<string, string>} [headers]
43
44
  * @returns {Promise<unknown>}
44
45
  */
45
- async function fetchJson(url) {
46
+ async function fetchJson(url, headers = {}) {
46
47
  const controller = new AbortController();
47
48
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
48
49
  try {
49
50
  const res = await fetch(url, {
50
- headers: { accept: 'application/json' },
51
+ headers: { accept: 'application/json', ...headers },
51
52
  signal: controller.signal,
52
53
  });
53
54
  if (!res.ok) {
@@ -161,3 +162,125 @@ export async function searchScope(scope, opts = {}) {
161
162
  result.sort((a, b) => a.name.localeCompare(b.name));
162
163
  return result;
163
164
  }
165
+
166
+ /**
167
+ * @typedef {{ distTags: DistTags, versions: string[] }} PackageInfo
168
+ */
169
+
170
+ /**
171
+ * Fetch the package manifest. Returns the dist-tags and a list of every
172
+ * published version sorted newest-first.
173
+ *
174
+ * Sort key is the registry's `time` map (ISO 8601 timestamps), which is
175
+ * format-independent — important because we use Beachball pre-release tags
176
+ * with custom identifiers like `2.2.4-platform-262.04-06-26-10-20` whose
177
+ * lexical order does not match chronological order. Falls back to
178
+ * `compareVersions` when the registry omits `time` entries.
179
+ *
180
+ * @param {string} packageName
181
+ * @returns {Promise<PackageInfo>}
182
+ */
183
+ export async function getPackageInfo(packageName) {
184
+ const url = `${getRegistryUrl()}/${encodeName(packageName)}`;
185
+ const data = /** @type {unknown} */ (await fetchJson(url));
186
+ if (!data || typeof data !== 'object') {
187
+ throw new RegistryError(`Malformed package response for ${packageName}`);
188
+ }
189
+ const rawTags = /** @type {{ 'dist-tags'?: unknown }} */ (data)['dist-tags'];
190
+ /** @type {DistTags} */
191
+ const distTags = {};
192
+ if (rawTags && typeof rawTags === 'object') {
193
+ for (const [tag, value] of Object.entries(rawTags)) {
194
+ if (typeof value === 'string') distTags[tag] = value;
195
+ }
196
+ }
197
+ const rawVersions = /** @type {{ versions?: unknown }} */ (data).versions;
198
+ /** @type {string[]} */
199
+ let versions = [];
200
+ if (rawVersions && typeof rawVersions === 'object') {
201
+ versions = Object.keys(rawVersions);
202
+ }
203
+
204
+ const rawTime = /** @type {{ time?: unknown }} */ (data).time;
205
+ /** @type {Record<string, string>} */
206
+ const timeMap = {};
207
+ if (rawTime && typeof rawTime === 'object') {
208
+ for (const [version, value] of Object.entries(rawTime)) {
209
+ if (typeof value === 'string') timeMap[version] = value;
210
+ }
211
+ }
212
+
213
+ versions.sort((a, b) => {
214
+ const ta = timeMap[a];
215
+ const tb = timeMap[b];
216
+ if (ta && tb) return tb.localeCompare(ta);
217
+ if (ta) return -1;
218
+ if (tb) return 1;
219
+ return compareVersions(b, a);
220
+ });
221
+ return { distTags, versions };
222
+ }
223
+
224
+ /**
225
+ * Compare two semver-ish version strings. Returns negative when `a < b`,
226
+ * positive when `a > b`, zero when equal. Pre-release tail sorts below the
227
+ * matching release (`1.0.0-next.0` < `1.0.0`). Numeric pre-release
228
+ * identifiers compare numerically; alphanumeric ones compare lexically.
229
+ *
230
+ * @param {string} a
231
+ * @param {string} b
232
+ */
233
+ export function compareVersions(a, b) {
234
+ const [coreA, preA = ''] = a.split('-');
235
+ const [coreB, preB = ''] = b.split('-');
236
+ const coreCmp = compareCore(coreA ?? '', coreB ?? '');
237
+ if (coreCmp !== 0) return coreCmp;
238
+ if (preA === preB) return 0;
239
+ if (preA === '') return 1;
240
+ if (preB === '') return -1;
241
+ return comparePrerelease(preA, preB);
242
+ }
243
+
244
+ /**
245
+ * @param {string} a
246
+ * @param {string} b
247
+ */
248
+ function compareCore(a, b) {
249
+ const partsA = a.split('.').map((n) => parseInt(n, 10) || 0);
250
+ const partsB = b.split('.').map((n) => parseInt(n, 10) || 0);
251
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
252
+ const ai = partsA[i] ?? 0;
253
+ const bi = partsB[i] ?? 0;
254
+ if (ai !== bi) return ai - bi;
255
+ }
256
+ return 0;
257
+ }
258
+
259
+ /**
260
+ * @param {string} a
261
+ * @param {string} b
262
+ */
263
+ function comparePrerelease(a, b) {
264
+ const partsA = a.split('.');
265
+ const partsB = b.split('.');
266
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
267
+ const ai = partsA[i];
268
+ const bi = partsB[i];
269
+ if (ai === bi) continue;
270
+ if (ai === undefined) return -1;
271
+ if (bi === undefined) return 1;
272
+ const ni = Number(ai);
273
+ const nj = Number(bi);
274
+ const aIsNum = !Number.isNaN(ni);
275
+ const bIsNum = !Number.isNaN(nj);
276
+ if (aIsNum && bIsNum) {
277
+ if (ni !== nj) return ni - nj;
278
+ continue;
279
+ }
280
+ if (aIsNum) return -1;
281
+ if (bIsNum) return 1;
282
+ const cmp = ai.localeCompare(bi);
283
+ if (cmp !== 0) return cmp;
284
+ }
285
+ return 0;
286
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@digigov/cli",
3
3
  "description": "CLI for Digigov apps and libs with plugin support",
4
- "version": "2.2.4-platform-262.04-06-26-10-20",
4
+ "version": "2.3.0",
5
5
  "author": "GRNET Devs <devs@lists.grnet.gr>",
6
6
  "type": "module",
7
7
  "bin": {