@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.
- package/commands/packages/doctor.js +18 -16
- package/commands/packages/init.js +43 -47
- package/commands/packages/update.js +125 -63
- package/lib/pm-detect.js +2 -11
- package/lib/registry.js +125 -2
- package/package.json +1 -1
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import chalk from
|
|
4
|
-
import { DigigovCommand } from
|
|
5
|
-
import { logger } from
|
|
6
|
-
import { findPackageJson } from
|
|
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
|
|
10
|
+
} from "../../lib/scope-scan.js";
|
|
11
11
|
|
|
12
12
|
export function createDoctorCommand() {
|
|
13
|
-
const cmd = new DigigovCommand(
|
|
13
|
+
const cmd = new DigigovCommand("doctor");
|
|
14
14
|
cmd
|
|
15
|
-
.description(
|
|
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(
|
|
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(
|
|
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 ===
|
|
43
|
+
(v) => typeof v === "string"
|
|
42
44
|
),
|
|
43
45
|
);
|
|
44
46
|
|
|
45
47
|
if (installedVersions.length === 0) {
|
|
46
48
|
logger.warn(
|
|
47
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
2
|
-
import * as p from
|
|
3
|
-
import ora from
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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(
|
|
11
|
+
const cmd = new DigigovCommand("init");
|
|
13
12
|
cmd
|
|
14
|
-
.description(
|
|
15
|
-
.option(
|
|
16
|
-
.option(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
53
|
-
|
|
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:
|
|
52
|
+
stdio: "inherit",
|
|
59
53
|
});
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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:
|
|
114
|
+
message: "Which release stream?",
|
|
117
115
|
options: [
|
|
118
|
-
{ value:
|
|
119
|
-
{ value:
|
|
116
|
+
{ value: "latest", label: "Stable (latest)" },
|
|
117
|
+
{ value: "next", label: "Pre-release (next)" },
|
|
120
118
|
],
|
|
121
|
-
initialValue:
|
|
119
|
+
initialValue: "latest",
|
|
122
120
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
2
|
-
import { execa } from
|
|
3
|
-
import * as p from
|
|
4
|
-
import ora from
|
|
5
|
-
import chalk from
|
|
6
|
-
import { DigigovCommand } from
|
|
7
|
-
import { logger } from
|
|
8
|
-
import { findPackageJson } from
|
|
9
|
-
import { detectPackageManager, buildInstallArgs } from
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
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(
|
|
19
|
+
const cmd = new DigigovCommand("update");
|
|
16
20
|
cmd
|
|
17
|
-
.description(
|
|
18
|
-
.option(
|
|
19
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
83
|
+
logger.log(`Reinstalling via ${pm}…`);
|
|
72
84
|
try {
|
|
73
|
-
await execa(pm, buildInstallArgs(
|
|
85
|
+
await execa(pm, buildInstallArgs(), {
|
|
74
86
|
cwd: root,
|
|
75
|
-
stdio:
|
|
87
|
+
stdio: "inherit",
|
|
76
88
|
});
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
137
|
+
let info;
|
|
138
|
+
const spinner = ora({
|
|
139
|
+
text: `Fetching ${VERSION_LIMIT} most recent versions from the registry…`,
|
|
140
|
+
}).start();
|
|
114
141
|
try {
|
|
115
|
-
|
|
142
|
+
info = await getPackageInfo(VERSION_ORACLE_PACKAGE);
|
|
143
|
+
spinner.stop();
|
|
116
144
|
} catch (err) {
|
|
117
|
-
|
|
118
|
-
|
|
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 (
|
|
131
|
-
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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} */ (
|
|
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(
|
|
66
|
-
|
|
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