@edspencer/paddock 0.82.0 → 0.83.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/package.json +1 -1
- package/packages/server/dist/cli/args.d.ts +29 -4
- package/packages/server/dist/cli/args.js +145 -7
- package/packages/server/dist/cli/busy-command.js +1 -1
- package/packages/server/dist/cli/busy.js +10 -12
- package/packages/server/dist/cli/doctor.d.ts +60 -0
- package/packages/server/dist/cli/doctor.js +580 -0
- package/packages/server/dist/cli/exit-codes.d.ts +21 -45
- package/packages/server/dist/cli/exit-codes.js +42 -45
- package/packages/server/dist/cli/output.js +5 -6
- package/packages/server/dist/cli/paddock.js +37 -12
- package/packages/server/dist/cli/service/index.d.ts +30 -2
- package/packages/server/dist/cli/service/index.js +163 -31
- package/packages/server/dist/cli/service/launchd.d.ts +31 -84
- package/packages/server/dist/cli/service/launchd.js +114 -304
- package/packages/server/dist/cli/service/logs.d.ts +9 -28
- package/packages/server/dist/cli/service/logs.js +33 -75
- package/packages/server/dist/cli/service/manager.d.ts +93 -0
- package/packages/server/dist/cli/service/manager.js +21 -0
- package/packages/server/dist/cli/service/path-env.d.ts +7 -18
- package/packages/server/dist/cli/service/path-env.js +30 -81
- package/packages/server/dist/cli/service/ready.d.ts +16 -23
- package/packages/server/dist/cli/service/ready.js +42 -0
- package/packages/server/dist/cli/service/spec.d.ts +4 -31
- package/packages/server/dist/cli/service/spec.js +9 -0
- package/packages/server/dist/cli/service/systemd.d.ts +27 -101
- package/packages/server/dist/cli/service/systemd.js +118 -261
- package/packages/server/dist/cli/update/health.d.ts +48 -0
- package/packages/server/dist/cli/update/health.js +64 -0
- package/packages/server/dist/cli/update/install-method.d.ts +29 -76
- package/packages/server/dist/cli/update/install-method.js +103 -178
- package/packages/server/dist/cli/update/lock.d.ts +7 -12
- package/packages/server/dist/cli/update/lock.js +28 -62
- package/packages/server/dist/cli/update/package-manager.d.ts +39 -74
- package/packages/server/dist/cli/update/package-manager.js +95 -160
- package/packages/server/dist/cli/update/registry.d.ts +17 -54
- package/packages/server/dist/cli/update/registry.js +46 -89
- package/packages/server/dist/cli/update/semver.d.ts +18 -41
- package/packages/server/dist/cli/update/semver.js +74 -124
- package/packages/server/dist/cli/update/update-check.d.ts +15 -26
- package/packages/server/dist/cli/update/update-check.js +30 -60
- package/packages/server/dist/cli/update/update.d.ts +57 -91
- package/packages/server/dist/cli/update/update.js +230 -431
- package/packages/server/dist/cli/update-command.js +3 -5
- package/packages/server/dist/runtime-file.d.ts +1 -1
- package/packages/server/dist/runtime-file.js +9 -13
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-CphdGB3R.js → DeleteProjectDialog-B07MKgv4.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-D5C2fkeQ.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-B_kcF4sm.js → DiscoverView-DpAAyBEf.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CvUK5DKW.js → InstanceConfigPage-YX0I_jVA.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-DvZ-j_Ao.js → MarkdownRenderer-C-CUuFhT.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DayHvUH2.js → ProjectRedirect-qyXbTK56.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-CCw83gUi.js → ProjectView-CguNOv8k.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-B3Klg2H4.js → ProjectsGrid-CH2Lz2Hf.js} +1 -1
- package/packages/web/dist/assets/{RootHome-BxcDvpgI.js → RootHome-CUGUc8za.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-7UlhFBSG.js → Surfaces-11or1q_3.js} +1 -1
- package/packages/web/dist/assets/{arc-DNkh5o5Q.js → arc-CQHeCzrk.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BrgN3oLF.js → architectureDiagram-3BPJPVTR-dGcuFxAa.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-D05XOJSk.js → blockDiagram-GPEHLZMM-CGc9h97S.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CKaMBhMP.js → c4Diagram-AAUBKEIU-BpX0sqhH.js} +1 -1
- package/packages/web/dist/assets/channel-BAeygx-e.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-8hgadCQc.js → chunk-2J33WTMH-uORGdO9D.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CBfEC-pu.js → chunk-4BX2VUAB-C8zI2PFy.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DV1eMgHk.js → chunk-55IACEB6-D22Iv6j6.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Cfgl5fxY.js → chunk-727SXJPM-DdbHXAtS.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BXhqD295.js → chunk-AQP2D5EJ-BeKbob3a.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-TFGOPtek.js → chunk-FMBD7UC4-DtLvFU_y.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-CHFM665R.js → chunk-ND2GUHAM-BWJFo6B1.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-CqEf6MMT.js → chunk-QZHKN3VN-DeFkNg3S.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-Cq2-sBMa.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-Cq2-sBMa.js +2 -0
- package/packages/web/dist/assets/{core-DmEaKxBb.js → core-CTNNIHx5.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BnGI6jkr.js → cose-bilkent-S5V4N54A-BmtlyA-v.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-CKoPx61E.js → dagre-BM42HDAG-oS3rC9CI.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-mMmtlBl1.js → diagram-2AECGRRQ-BzIBqz6A.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Cdz9Mtnt.js → diagram-5GNKFQAL-BgIT2g8R.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-DQxM3wYj.js → diagram-KO2AKTUF-DACT41Re.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BWrinPsq.js → diagram-LMA3HP47-BstA3UkI.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-U6MYUve2.js → diagram-OG6HWLK6-Dp3Cn5n_.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-DGjRXuTP.js → documentTitle-DRSQmA8q.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-xqMCsXxY.js → erDiagram-TEJ5UH35-wf8PyGx_.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-PMfy_czF.js → flowDiagram-I6XJVG4X-CJ4xx6EQ.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-whhYYE4N.js → ganttDiagram-6RSMTGT7-z9obPrPM.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-C51JW4tf.js → gitGraphDiagram-PVQCEYII-Cf4Cu7Tk.js} +1 -1
- package/packages/web/dist/assets/{index-CHOtvlnO.js → index-Bsv-raz2.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-SH8cr905.js → infoDiagram-5YYISTIA-BIfuiYxX.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-BjqaoU3U.js → ishikawaDiagram-YF4QCWOH-1pACxMTX.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CBmdEVrd.js → journeyDiagram-JHISSGLW-XGbzfw-v.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-U8njjMLZ.js → kanban-definition-UN3LZRKU-Bvl-nMak.js} +1 -1
- package/packages/web/dist/assets/{linear-DF66GPs1.js → linear-uNToO4bv.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CouJz7Ph.js → mermaid.core-CrChvyj6.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BYIf0oFt.js → mindmap-definition-RKZ34NQL-C6DaQ4Fm.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Cm8rgg09.js → pieDiagram-4H26LBE5-CEh8lKyF.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-C3Il5kOH.js → quadrantDiagram-W4KKPZXB-z4zvICSM.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D3PE2nTe.js → requirementDiagram-4Y6WPE33-BBltkjsj.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DdmvBM18.js → sankeyDiagram-5OEKKPKP-D0s95wQ4.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-B8M_GAIq.js → sequenceDiagram-3UESZ5HK-kQa65Nbt.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DZkXnK1_.js → stateDiagram-AJRCARHV-BqVIoyED.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-B6OXxEra.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-IHXq7rqi.js → timeline-definition-PNZ67QCA-Bo5L62WI.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-Dga7tLtT.js → vennDiagram-CIIHVFJN-CN9rH4lT.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CmcwnEJ-.js → wardley-L42UT6IY-BKhnD_W_.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-zHuSLrD4.js → wardleyDiagram-YWT4CUSO-XHTbZzXj.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-D93MU_ic.js → xychartDiagram-2RQKCTM6--t9XJLbR.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/server/dist/cli/service/backend.d.ts +0 -115
- package/packages/server/dist/cli/service/backend.js +0 -19
- package/packages/web/dist/assets/DiscoverPage-C4OPLNO8.js +0 -2
- package/packages/web/dist/assets/channel-JrEedxB8.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CHJiQp-W.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CHJiQp-W.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CjCdT9Ls.js +0 -2
|
@@ -1,107 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* Windows is the exception and has to be: `npm` there is `npm.cmd`, which
|
|
15
|
-
* Node refuses to spawn without a shell since its fix for CVE-2024-27980
|
|
16
|
-
* (SPEC §7). That path quotes its arguments itself.
|
|
17
|
-
* - **Can we write there?** SPEC §5 step 5. Asked before anything is stopped,
|
|
18
|
-
* and answered by actually creating a file rather than by `access()` alone —
|
|
19
|
-
* see {@link isWritable}.
|
|
20
|
-
*
|
|
21
|
-
* Generic: wave C4 lifts this into `reference/`. Node built-ins only.
|
|
22
|
-
*/
|
|
23
|
-
import { spawnSync } from "node:child_process";
|
|
24
|
-
import fs from "node:fs";
|
|
25
|
-
import os from "node:os";
|
|
26
|
-
import path from "node:path";
|
|
1
|
+
// From edspencer/cli-contract reference/package-manager.ts @ 0.2; local changes: `.ts` import specifiers → `.js`, and `exit-codes`, `output` and `runtime-file` imported from where Paddock keeps them (`../`, `../../`).
|
|
2
|
+
// Running the package manager (SPEC §5 steps 5, 8 and 10, §6, §7). Node built-ins only.
|
|
3
|
+
//
|
|
4
|
+
// - **npm is the one beside `process.execPath`**, run as `node <npm-cli.js>`, never whichever `npm` is first on PATH.
|
|
5
|
+
// Under nvm, fnm or volta those are different npms for different Nodes, and the wrong one's `npm root -g` is a
|
|
6
|
+
// global folder this install isn't in: an ordinary global install would read as a dependency and refuse to update.
|
|
7
|
+
// Running `npm-cli.js` with this Node also needs no shell on Windows, where `npm.cmd` would (CVE-2024-27980).
|
|
8
|
+
// - **No shell otherwise**, so a path or a name with a space in it can't become two arguments. pnpm, bun and volta on
|
|
9
|
+
// Windows are `.cmd` files and do need one; their arguments are quoted for `cmd.exe` here.
|
|
10
|
+
// - **Writable means a file can be created**, not just that `access()` says yes (`isWritable`).
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
27
14
|
export const spawnSyncRunner = (cmd, args, opts = {}) => {
|
|
28
15
|
const r = spawnSync(cmd, args, {
|
|
29
|
-
encoding:
|
|
16
|
+
encoding: 'utf8',
|
|
30
17
|
...(opts.env !== undefined ? { env: opts.env } : {}),
|
|
31
18
|
...(opts.shell === true ? { shell: true } : {}),
|
|
32
19
|
...(opts.timeoutMs !== undefined ? { timeout: opts.timeoutMs } : {}),
|
|
33
20
|
maxBuffer: 32 * 1024 * 1024,
|
|
21
|
+
windowsHide: true,
|
|
34
22
|
});
|
|
35
|
-
return { status: r.status, stdout: r.stdout ??
|
|
23
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? (r.error ? String(r.error.message) : '') };
|
|
36
24
|
};
|
|
25
|
+
/** How long an install may take. */
|
|
26
|
+
export const INSTALL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
37
27
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Falls back to the bare name — if the sibling is not there (a distro that
|
|
42
|
-
* splits node and npm into separate packages), PATH is the only answer left and
|
|
43
|
-
* is better than failing.
|
|
28
|
+
* How to run the npm that belongs to this Node: `[execPath, npm-cli.js]`, found beside it (`<dir>/npm` is a symlink
|
|
29
|
+
* to it on POSIX; on Windows it's `<dir>/node_modules/npm/bin/npm-cli.js`). Falls back to `npm` on the PATH, for a
|
|
30
|
+
* distribution that packages npm separately.
|
|
44
31
|
*/
|
|
45
|
-
export function
|
|
32
|
+
export function npmCommand(ctx = {}) {
|
|
33
|
+
const execPath = ctx.execPath ?? process.execPath;
|
|
34
|
+
const platform = ctx.platform ?? process.platform;
|
|
35
|
+
const exists = ctx.exists ?? ((p) => fs.existsSync(p));
|
|
36
|
+
const realpath = ctx.realpath ?? ((p) => fs.realpathSync(p));
|
|
46
37
|
const dir = path.dirname(execPath);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
const candidates = platform === 'win32'
|
|
39
|
+
? [path.join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js')]
|
|
40
|
+
: [path.join(dir, 'npm'), path.join(dir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js')];
|
|
41
|
+
for (const c of candidates) {
|
|
42
|
+
if (!exists(c))
|
|
43
|
+
continue;
|
|
44
|
+
let real = c;
|
|
45
|
+
try {
|
|
46
|
+
real = realpath(c);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// keep the candidate
|
|
50
|
+
}
|
|
51
|
+
if (real.endsWith('.js'))
|
|
52
|
+
return { cmd: execPath, pre: [real], shell: false };
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
/* fall through */
|
|
54
|
-
}
|
|
55
|
-
return platform === "win32" ? "npm.cmd" : "npm";
|
|
54
|
+
return platform === 'win32' ? { cmd: 'npm.cmd', pre: [], shell: true } : { cmd: 'npm', pre: [], shell: false };
|
|
56
55
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
56
|
+
/** Quotes each word for `cmd.exe`, for the one case that needs a shell. */
|
|
57
|
+
function cmdLine(cmd, args) {
|
|
58
|
+
const q = (a) => (/^[\w@./:=+-]+$/.test(a) ? a : `"${a.replace(/"/g, '""')}"`);
|
|
59
|
+
return { cmd: [cmd, ...args].map(q).join(' '), args: [] };
|
|
60
|
+
}
|
|
61
|
+
function runWith(cmd, args, shell, ctx, timeoutMs) {
|
|
61
62
|
const run = ctx.spawn ?? spawnSyncRunner;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// The ONE place a shell is used, and only where Node leaves no choice. The
|
|
65
|
-
// arguments passed through here are package specs and npm flags we build
|
|
66
|
-
// ourselves, never user text.
|
|
67
|
-
...(platform === "win32" ? { shell: true } : {}),
|
|
68
|
-
timeoutMs: 15 * 60 * 1000,
|
|
69
|
-
});
|
|
63
|
+
const line = shell ? cmdLine(cmd, args) : { cmd, args };
|
|
64
|
+
return run(line.cmd, line.args, { ...(ctx.env !== undefined ? { env: ctx.env } : {}), ...(shell ? { shell: true } : {}), timeoutMs });
|
|
70
65
|
}
|
|
71
|
-
/**
|
|
72
|
-
export function
|
|
73
|
-
const
|
|
66
|
+
/** Runs this Node's npm. */
|
|
67
|
+
export function runNpm(args, ctx = {}, timeoutMs = INSTALL_TIMEOUT_MS) {
|
|
68
|
+
const { cmd, pre, shell } = npmCommand(ctx);
|
|
69
|
+
return runWith(cmd, [...pre, ...args], shell, ctx, timeoutMs);
|
|
70
|
+
}
|
|
71
|
+
const lastLine = (r) => {
|
|
74
72
|
if (r.status !== 0)
|
|
75
73
|
return undefined;
|
|
76
|
-
const out = r.stdout.trim().split(
|
|
77
|
-
return out !== undefined && out !==
|
|
74
|
+
const out = r.stdout.trim().split(/\r?\n/).at(-1)?.trim();
|
|
75
|
+
return out !== undefined && out !== '' && out !== 'undefined' && out !== 'null' ? out : undefined;
|
|
76
|
+
};
|
|
77
|
+
/** `npm root -g`, or undefined when npm can't answer. */
|
|
78
|
+
export function npmGlobalRoot(ctx = {}) {
|
|
79
|
+
return lastLine(runNpm(['root', '-g'], ctx, 60_000));
|
|
78
80
|
}
|
|
79
|
-
/** `npm config get <key>`, or undefined.
|
|
81
|
+
/** `npm config get <key>`, or undefined. */
|
|
80
82
|
export function npmConfigGet(key, ctx = {}) {
|
|
81
|
-
|
|
82
|
-
if (r.status !== 0)
|
|
83
|
-
return undefined;
|
|
84
|
-
const out = r.stdout.trim().split("\n").at(-1)?.trim();
|
|
85
|
-
return out !== undefined && out !== "" && out !== "undefined" && out !== "null" ? out : undefined;
|
|
83
|
+
return lastLine(runNpm(['config', 'get', key], ctx, 60_000));
|
|
86
84
|
}
|
|
87
85
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* this check. It consults the mode bits, which say yes for root and say
|
|
92
|
-
* nothing about a read-only mount, an immutable flag, or an ACL — the usual
|
|
93
|
-
* shapes of "npm install -g needs sudo". So the directory is probed by
|
|
94
|
-
* CREATING something in it and removing it again, which is the same syscall
|
|
95
|
-
* npm is about to make.
|
|
96
|
-
*
|
|
97
|
-
* A missing directory walks up to its nearest existing parent: npm would create
|
|
98
|
-
* it, and whether it can is the parent's question.
|
|
86
|
+
* Whether `dir` is really writable (SPEC §5 step 5): a folder is created in it and removed again, the same call npm
|
|
87
|
+
* is about to make. `access(W_OK)` alone says yes to root on a read-only mount, and nothing about ACLs or immutable
|
|
88
|
+
* flags. A folder that doesn't exist yet asks its nearest existing parent, since npm would create it.
|
|
99
89
|
*/
|
|
100
90
|
export function isWritable(dir) {
|
|
101
91
|
let probe = dir;
|
|
102
|
-
|
|
103
|
-
if (fs.existsSync(probe))
|
|
104
|
-
break;
|
|
92
|
+
while (!fs.existsSync(probe)) {
|
|
105
93
|
const parent = path.dirname(probe);
|
|
106
94
|
if (parent === probe)
|
|
107
95
|
return false;
|
|
@@ -109,13 +97,7 @@ export function isWritable(dir) {
|
|
|
109
97
|
}
|
|
110
98
|
try {
|
|
111
99
|
fs.accessSync(probe, fs.constants.W_OK);
|
|
112
|
-
|
|
113
|
-
catch {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const tmp = fs.mkdtempSync(path.join(probe, ".update-probe-"));
|
|
118
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
100
|
+
fs.rmSync(fs.mkdtempSync(path.join(probe, '.update-probe-')), { recursive: true, force: true });
|
|
119
101
|
return true;
|
|
120
102
|
}
|
|
121
103
|
catch {
|
|
@@ -123,99 +105,52 @@ export function isWritable(dir) {
|
|
|
123
105
|
}
|
|
124
106
|
}
|
|
125
107
|
/**
|
|
126
|
-
* Where a
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Only npm can be asked properly (`npm root -g`). For the others the install
|
|
130
|
-
* command's own target is a good enough proxy — and if it is wrong, the install
|
|
131
|
-
* fails with that manager's own message and the rollback runs, which is a worse
|
|
132
|
-
* experience than the sudo hint but not a worse outcome.
|
|
108
|
+
* Where a manager would install, for `isWritable` to ask about. Only npm can be asked (`npm root -g`). For the others
|
|
109
|
+
* there's no answer, and an install that can't write fails with that manager's own message, and rolls back.
|
|
133
110
|
*/
|
|
134
111
|
export function globalRootFor(manager, ctx = {}) {
|
|
135
|
-
|
|
136
|
-
return npmGlobalRoot(ctx);
|
|
137
|
-
return undefined;
|
|
112
|
+
return manager === 'npm' ? npmGlobalRoot(ctx) : undefined;
|
|
138
113
|
}
|
|
139
|
-
/** The
|
|
114
|
+
/** The global install command, per SPEC §6's last column. */
|
|
140
115
|
export function installArgs(manager, spec) {
|
|
141
116
|
switch (manager) {
|
|
142
|
-
case
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return { cmd:
|
|
146
|
-
case
|
|
147
|
-
return { cmd:
|
|
148
|
-
case
|
|
149
|
-
return { cmd:
|
|
150
|
-
case "volta":
|
|
151
|
-
return { cmd: "volta", args: ["install", spec] };
|
|
117
|
+
case 'npm':
|
|
118
|
+
return { cmd: 'npm', args: ['install', '--global', '--no-audit', '--no-fund', spec] };
|
|
119
|
+
case 'pnpm':
|
|
120
|
+
return { cmd: 'pnpm', args: ['add', '-g', spec] };
|
|
121
|
+
case 'bun':
|
|
122
|
+
return { cmd: 'bun', args: ['add', '-g', spec] };
|
|
123
|
+
case 'volta':
|
|
124
|
+
return { cmd: 'volta', args: ['install', spec] };
|
|
152
125
|
}
|
|
153
126
|
}
|
|
154
|
-
/**
|
|
155
|
-
* Install `<name>@<version>` with the manager this install came from.
|
|
156
|
-
*
|
|
157
|
-
* npm goes through {@link runNpm}, so it is the npm beside this Node. The other
|
|
158
|
-
* three are looked up on PATH, which is the only place they are: pnpm, bun and
|
|
159
|
-
* volta are not installed beside node.
|
|
160
|
-
*/
|
|
127
|
+
/** Installs `<name>@<version>` with the manager this install came from. pnpm, bun and volta come from the PATH. */
|
|
161
128
|
export function installPackage(o) {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
...(o.spawn !== undefined ? { spawn: o.spawn } : {}),
|
|
167
|
-
...(o.env !== undefined ? { env: o.env } : {}),
|
|
168
|
-
...(o.execPath !== undefined ? { execPath: o.execPath } : {}),
|
|
169
|
-
...(o.platform !== undefined ? { platform: o.platform } : {}),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
const run = o.spawn ?? spawnSyncRunner;
|
|
173
|
-
return run(cmd, args, {
|
|
174
|
-
...(o.env !== undefined ? { env: o.env } : {}),
|
|
175
|
-
...((o.platform ?? process.platform) === "win32" ? { shell: true } : {}),
|
|
176
|
-
timeoutMs: 15 * 60 * 1000,
|
|
177
|
-
});
|
|
129
|
+
const { cmd, args } = installArgs(o.manager, `${o.packageName}@${o.version}`);
|
|
130
|
+
if (o.manager === 'npm')
|
|
131
|
+
return runNpm(args, o);
|
|
132
|
+
return runWith(cmd, args, (o.platform ?? process.platform) === 'win32', o, INSTALL_TIMEOUT_MS);
|
|
178
133
|
}
|
|
179
134
|
/**
|
|
180
|
-
* `npm cache add <name>@<version>`
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* Both the target and the CURRENT version are cached, and caching the current
|
|
184
|
-
* one is the half that is easy to leave out and expensive to miss: it is what
|
|
185
|
-
* gives the rollback a package to reinstall when the reason the update failed
|
|
186
|
-
* was the network. Done while the old version is still serving, so a failure
|
|
187
|
-
* here costs nothing but a message.
|
|
188
|
-
*
|
|
189
|
-
* `npm cache add` is used whatever the manager is, because every one of these
|
|
190
|
-
* layouts has an npm available (it ships with Node) and the point is only to
|
|
191
|
-
* have the tarball on the machine. A failure is reported and not fatal — the
|
|
192
|
-
* install below may still succeed from the network.
|
|
135
|
+
* `npm cache add <name>@<version>` (SPEC §5 step 8), for the target and for the current version, while the old one
|
|
136
|
+
* still serves. Caching the current one is what lets a rollback reinstall it when the network is what failed. npm
|
|
137
|
+
* is used whatever the manager: it ships with Node, and the point is only to have the tarball on the machine.
|
|
193
138
|
*/
|
|
194
139
|
export function cacheAdd(packageName, version, ctx = {}) {
|
|
195
|
-
return runNpm([
|
|
140
|
+
return runNpm(['cache', 'add', `${packageName}@${version}`], ctx);
|
|
196
141
|
}
|
|
197
142
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* before it hands over to the new CLI. The alternative — reading the
|
|
202
|
-
* `package.json` next to it — answers a different question: npm writes the
|
|
203
|
-
* manifest and the files in one operation, but a partially-updated global
|
|
204
|
-
* folder, a stale symlink, or a pnpm layout whose versioned realpath moved all
|
|
205
|
-
* show up here and not there.
|
|
143
|
+
* A CLI's version, by running it: `node <cli> --version`. This is how `update` shows the install replaced the files
|
|
144
|
+
* before it hands over. A `package.json` beside it answers a different question: a stale symlink, or a pnpm layout
|
|
145
|
+
* whose versioned path moved, shows up here and not there.
|
|
206
146
|
*/
|
|
207
147
|
export function cliVersion(cliPath, ctx = {}) {
|
|
208
|
-
const
|
|
209
|
-
const r = run(ctx.execPath ?? process.execPath, [cliPath, "--version"], {
|
|
148
|
+
const r = (ctx.spawn ?? spawnSyncRunner)(ctx.execPath ?? process.execPath, [cliPath, '--version'], {
|
|
210
149
|
...(ctx.env !== undefined ? { env: ctx.env } : {}),
|
|
211
150
|
timeoutMs: 60_000,
|
|
212
151
|
});
|
|
213
152
|
if (r.status !== 0)
|
|
214
153
|
return undefined;
|
|
215
|
-
const line = r.stdout.trim().split(
|
|
154
|
+
const line = r.stdout.trim().split(/\r?\n/)[0]?.trim();
|
|
216
155
|
return line !== undefined && /^\d+\.\d+\.\d+/.test(line) ? line : undefined;
|
|
217
156
|
}
|
|
218
|
-
/** A throwaway directory, for callers that need one (the writability probe's sibling). */
|
|
219
|
-
export function tmpDir(prefix) {
|
|
220
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
221
|
-
}
|
|
@@ -1,55 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The npm registry, as `update` asks it (CLI contract SPEC §5 step 3).
|
|
3
|
-
*
|
|
4
|
-
* Two requests, both with `fetch` and a 10-second abort, and nothing else:
|
|
5
|
-
*
|
|
6
|
-
* GET <registry>/-/package/<name>/dist-tags which version is `latest`
|
|
7
|
-
* GET <registry>/<name>/<version> that version's `engines`
|
|
8
|
-
*
|
|
9
|
-
* Deliberately NOT `npm view`: spawning npm to read two small JSON documents
|
|
10
|
-
* costs a second of process start-up, needs npm to be on PATH at all, and puts
|
|
11
|
-
* the registry's answer through a program whose output format is not a
|
|
12
|
-
* contract. `fetch` is in Node 22 and the registry's REST surface has been
|
|
13
|
-
* stable for a decade.
|
|
14
|
-
*
|
|
15
|
-
* **Nothing here is called except by a person.** SPEC §3: only `update`,
|
|
16
|
-
* `update --check` and `doctor` contact the registry, and only when someone
|
|
17
|
-
* runs them. There is no background check and no check at startup, which is why
|
|
18
|
-
* this module is reachable from the CLI and from nowhere in the server.
|
|
19
|
-
*
|
|
20
|
-
* Generic: wave C4 lifts it into `reference/registry.ts`.
|
|
21
|
-
*/
|
|
22
|
-
/** SPEC §5: 10 seconds, and it is the budget for each request rather than the pair. */
|
|
1
|
+
/** SPEC §5: 10 seconds for each request. */
|
|
23
2
|
export declare const REGISTRY_TIMEOUT_MS = 10000;
|
|
24
3
|
export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
25
4
|
export interface RegistryOptions {
|
|
26
5
|
packageName: string;
|
|
27
|
-
/**
|
|
6
|
+
/** The base URL; a trailing slash is fine. */
|
|
28
7
|
registry: string;
|
|
29
8
|
fetch?: typeof globalThis.fetch;
|
|
30
9
|
timeoutMs?: number;
|
|
31
10
|
}
|
|
32
11
|
/**
|
|
33
|
-
* Which registry to ask: `npm_config_registry`, else `npm config get registry`,
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* The first is what makes the fake-registry tests possible without touching a
|
|
37
|
-
* global npm config — an `npm_config_registry` in the environment is how npm
|
|
38
|
-
* itself takes the same setting, so a test and a real `.npmrc` arrive by the
|
|
39
|
-
* same door.
|
|
12
|
+
* Which registry to ask: `npm_config_registry`, else `npm config get registry`, else the public one (SPEC §5 step 3).
|
|
13
|
+
* The variable comes first because it's how npm itself takes the setting, so a test and a real `.npmrc` arrive the
|
|
14
|
+
* same way.
|
|
40
15
|
*/
|
|
41
16
|
export declare function resolveRegistry(env: NodeJS.ProcessEnv, npmConfigGet?: (key: string) => string | undefined): string;
|
|
42
|
-
/**
|
|
43
|
-
* Join a path onto the registry base.
|
|
44
|
-
*
|
|
45
|
-
* A scoped name's `/` is written `%2f` (SPEC §5 step 3). That is required by
|
|
46
|
-
* the dist-tags route and accepted by the manifest route, so both are built the
|
|
47
|
-
* same way — one rule instead of two, and no chance of only one of them being
|
|
48
|
-
* right for `@scope/name`.
|
|
49
|
-
*/
|
|
17
|
+
/** Joins path parts onto the registry, writing a scoped name's `/` as `%2f` (SPEC §5 step 3). Both routes take it. */
|
|
50
18
|
export declare function registryUrl(registry: string, ...parts: string[]): string;
|
|
51
|
-
/**
|
|
19
|
+
/** Anything that went wrong talking to the registry, with the URL, as one reportable error. */
|
|
52
20
|
export declare class RegistryError extends Error {
|
|
21
|
+
name: string;
|
|
53
22
|
readonly url: string;
|
|
54
23
|
constructor(url: string, message: string);
|
|
55
24
|
}
|
|
@@ -62,28 +31,22 @@ export interface PackageManifest {
|
|
|
62
31
|
node?: string;
|
|
63
32
|
};
|
|
64
33
|
}
|
|
65
|
-
/** One version's manifest
|
|
34
|
+
/** One version's manifest, read for `engines`. */
|
|
66
35
|
export declare function fetchManifest(o: RegistryOptions & {
|
|
67
36
|
version: string;
|
|
68
37
|
}): Promise<PackageManifest>;
|
|
69
|
-
export interface ResolveTargetInput extends RegistryOptions {
|
|
70
|
-
/** `--to <version|tag>`, or undefined for `latest`. */
|
|
71
|
-
to?: string;
|
|
72
|
-
}
|
|
73
38
|
export interface ResolvedTarget {
|
|
74
39
|
version: string;
|
|
75
|
-
/** The dist-tag asked for, or null when `--to` named a version
|
|
40
|
+
/** The dist-tag asked for, or null when `--to` named a version. */
|
|
76
41
|
tag: string | null;
|
|
42
|
+
/** The target's `engines.node`. */
|
|
77
43
|
engines: string | null;
|
|
78
44
|
}
|
|
79
45
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* asking: anything that parses as a version is a version, everything else is a
|
|
84
|
-
* tag. That ordering matters because a registry will happily serve a manifest
|
|
85
|
-
* for a tag name as well, and resolving `--to next` as a literal version would
|
|
86
|
-
* silently install whatever `next` pointed at while REPORTING the target as
|
|
87
|
-
* "next".
|
|
46
|
+
* `--to` (or `latest` without it) as one version, with its `engines`. A version and a tag are told apart by shape:
|
|
47
|
+
* anything that looks like a version is one. Asking first would be wrong, since a registry may serve a manifest for a
|
|
48
|
+
* tag's name too, and `--to next` would then install `next` while reporting it as a version called "next".
|
|
88
49
|
*/
|
|
89
|
-
export declare function resolveTarget(o:
|
|
50
|
+
export declare function resolveTarget(o: RegistryOptions & {
|
|
51
|
+
to?: string;
|
|
52
|
+
}): Promise<ResolvedTarget>;
|
|
@@ -1,60 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* the registry's answer through a program whose output format is not a
|
|
12
|
-
* contract. `fetch` is in Node 22 and the registry's REST surface has been
|
|
13
|
-
* stable for a decade.
|
|
14
|
-
*
|
|
15
|
-
* **Nothing here is called except by a person.** SPEC §3: only `update`,
|
|
16
|
-
* `update --check` and `doctor` contact the registry, and only when someone
|
|
17
|
-
* runs them. There is no background check and no check at startup, which is why
|
|
18
|
-
* this module is reachable from the CLI and from nowhere in the server.
|
|
19
|
-
*
|
|
20
|
-
* Generic: wave C4 lifts it into `reference/registry.ts`.
|
|
21
|
-
*/
|
|
22
|
-
/** SPEC §5: 10 seconds, and it is the budget for each request rather than the pair. */
|
|
1
|
+
// From edspencer/cli-contract reference/registry.ts @ 0.2; local changes: `.ts` import specifiers → `.js`.
|
|
2
|
+
// The npm registry, as `update` asks it (SPEC §5 step 3): two requests with `fetch` and a 10-second abort.
|
|
3
|
+
//
|
|
4
|
+
// GET <registry>/-/package/<name>/dist-tags which version `latest` (or `--to <tag>`) is
|
|
5
|
+
// GET <registry>/<name>/<version> that version's `engines`
|
|
6
|
+
//
|
|
7
|
+
// Not `npm view`: that's a second of start-up to read two small documents, through a program whose output isn't a
|
|
8
|
+
// contract. Only `update`, `update --check` and `doctor` call this, and only when a person runs them (SPEC §3).
|
|
9
|
+
// Node built-ins only.
|
|
10
|
+
/** SPEC §5: 10 seconds for each request. */
|
|
23
11
|
export const REGISTRY_TIMEOUT_MS = 10_000;
|
|
24
|
-
export const DEFAULT_REGISTRY =
|
|
12
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
25
13
|
/**
|
|
26
|
-
* Which registry to ask: `npm_config_registry`, else `npm config get registry`,
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* The first is what makes the fake-registry tests possible without touching a
|
|
30
|
-
* global npm config — an `npm_config_registry` in the environment is how npm
|
|
31
|
-
* itself takes the same setting, so a test and a real `.npmrc` arrive by the
|
|
32
|
-
* same door.
|
|
14
|
+
* Which registry to ask: `npm_config_registry`, else `npm config get registry`, else the public one (SPEC §5 step 3).
|
|
15
|
+
* The variable comes first because it's how npm itself takes the setting, so a test and a real `.npmrc` arrive the
|
|
16
|
+
* same way.
|
|
33
17
|
*/
|
|
34
18
|
export function resolveRegistry(env, npmConfigGet) {
|
|
35
19
|
const fromEnv = env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY;
|
|
36
|
-
if (fromEnv !== undefined && fromEnv.trim() !==
|
|
20
|
+
if (fromEnv !== undefined && fromEnv.trim() !== '')
|
|
37
21
|
return fromEnv.trim();
|
|
38
|
-
const fromNpm = npmConfigGet?.(
|
|
39
|
-
if (fromNpm !== undefined && fromNpm
|
|
40
|
-
return fromNpm
|
|
41
|
-
}
|
|
22
|
+
const fromNpm = npmConfigGet?.('registry')?.trim();
|
|
23
|
+
if (fromNpm !== undefined && fromNpm !== '' && fromNpm !== 'undefined')
|
|
24
|
+
return fromNpm;
|
|
42
25
|
return DEFAULT_REGISTRY;
|
|
43
26
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Join a path onto the registry base.
|
|
46
|
-
*
|
|
47
|
-
* A scoped name's `/` is written `%2f` (SPEC §5 step 3). That is required by
|
|
48
|
-
* the dist-tags route and accepted by the manifest route, so both are built the
|
|
49
|
-
* same way — one rule instead of two, and no chance of only one of them being
|
|
50
|
-
* right for `@scope/name`.
|
|
51
|
-
*/
|
|
27
|
+
/** Joins path parts onto the registry, writing a scoped name's `/` as `%2f` (SPEC §5 step 3). Both routes take it. */
|
|
52
28
|
export function registryUrl(registry, ...parts) {
|
|
53
|
-
|
|
54
|
-
return `${base}/${parts.map((p) => p.replace(/\//g, "%2f")).join("/")}`;
|
|
29
|
+
return `${registry.replace(/\/+$/, '')}/${parts.map((p) => p.replace(/\//g, '%2f')).join('/')}`;
|
|
55
30
|
}
|
|
56
|
-
/**
|
|
31
|
+
/** Anything that went wrong talking to the registry, with the URL, as one reportable error. */
|
|
57
32
|
export class RegistryError extends Error {
|
|
33
|
+
name = 'RegistryError';
|
|
58
34
|
url;
|
|
59
35
|
constructor(url, message) {
|
|
60
36
|
super(message);
|
|
@@ -62,83 +38,64 @@ export class RegistryError extends Error {
|
|
|
62
38
|
}
|
|
63
39
|
}
|
|
64
40
|
async function getJson(url, o) {
|
|
65
|
-
const
|
|
41
|
+
const timeoutMs = o.timeoutMs ?? REGISTRY_TIMEOUT_MS;
|
|
66
42
|
let res;
|
|
67
43
|
try {
|
|
68
|
-
res = await
|
|
69
|
-
signal: AbortSignal.timeout(o.timeoutMs ?? REGISTRY_TIMEOUT_MS),
|
|
70
|
-
headers: { accept: "application/json" },
|
|
71
|
-
});
|
|
44
|
+
res = await (o.fetch ?? globalThis.fetch)(url, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: 'application/json' } });
|
|
72
45
|
}
|
|
73
46
|
catch (err) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
throw new RegistryError(url, `could not reach the registry: ${why}`);
|
|
47
|
+
const name = err.name;
|
|
48
|
+
const why = name === 'TimeoutError' || name === 'AbortError' ? `no answer within ${timeoutMs / 1000} seconds` : (err.message ?? String(err));
|
|
49
|
+
throw new RegistryError(url, `couldn't reach the registry: ${why}`);
|
|
78
50
|
}
|
|
79
51
|
if (res.status === 404)
|
|
80
|
-
throw new RegistryError(url,
|
|
52
|
+
throw new RegistryError(url, 'the registry has no such package or version');
|
|
81
53
|
if (!res.ok)
|
|
82
54
|
throw new RegistryError(url, `the registry answered HTTP ${res.status}`);
|
|
83
55
|
try {
|
|
84
56
|
return (await res.json());
|
|
85
57
|
}
|
|
86
58
|
catch {
|
|
87
|
-
throw new RegistryError(url, "the registry's answer
|
|
59
|
+
throw new RegistryError(url, "the registry's answer isn't JSON");
|
|
88
60
|
}
|
|
89
61
|
}
|
|
90
62
|
/** `{ latest: "0.81.0", next: "0.82.0-rc.1" }`. */
|
|
91
63
|
export async function fetchDistTags(o) {
|
|
92
|
-
const url = registryUrl(o.registry,
|
|
93
|
-
const body = await getJson(url, o);
|
|
64
|
+
const url = registryUrl(o.registry, '-', 'package', o.packageName, 'dist-tags');
|
|
94
65
|
const tags = {};
|
|
95
|
-
for (const [k, v] of Object.entries(
|
|
96
|
-
if (typeof v ===
|
|
66
|
+
for (const [k, v] of Object.entries(await getJson(url, o)))
|
|
67
|
+
if (typeof v === 'string')
|
|
97
68
|
tags[k] = v;
|
|
98
69
|
if (Object.keys(tags).length === 0)
|
|
99
|
-
throw new RegistryError(url,
|
|
70
|
+
throw new RegistryError(url, 'the registry lists no dist-tags');
|
|
100
71
|
return tags;
|
|
101
72
|
}
|
|
102
|
-
/** One version's manifest
|
|
103
|
-
export
|
|
104
|
-
|
|
105
|
-
return getJson(url, o);
|
|
73
|
+
/** One version's manifest, read for `engines`. */
|
|
74
|
+
export function fetchManifest(o) {
|
|
75
|
+
return getJson(registryUrl(o.registry, o.packageName, o.version), o);
|
|
106
76
|
}
|
|
107
77
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* asking: anything that parses as a version is a version, everything else is a
|
|
112
|
-
* tag. That ordering matters because a registry will happily serve a manifest
|
|
113
|
-
* for a tag name as well, and resolving `--to next` as a literal version would
|
|
114
|
-
* silently install whatever `next` pointed at while REPORTING the target as
|
|
115
|
-
* "next".
|
|
78
|
+
* `--to` (or `latest` without it) as one version, with its `engines`. A version and a tag are told apart by shape:
|
|
79
|
+
* anything that looks like a version is one. Asking first would be wrong, since a registry may serve a manifest for a
|
|
80
|
+
* tag's name too, and `--to next` would then install `next` while reporting it as a version called "next".
|
|
116
81
|
*/
|
|
117
82
|
export async function resolveTarget(o) {
|
|
118
|
-
const
|
|
83
|
+
const to = o.to?.trim();
|
|
119
84
|
let version;
|
|
120
85
|
let tag;
|
|
121
|
-
if (to
|
|
122
|
-
|
|
123
|
-
const latest = tags.latest;
|
|
124
|
-
if (latest === undefined) {
|
|
125
|
-
throw new RegistryError(registryUrl(o.registry, "-", "package", o.packageName, "dist-tags"), "the registry has no `latest` dist-tag for this package");
|
|
126
|
-
}
|
|
127
|
-
version = latest;
|
|
128
|
-
tag = "latest";
|
|
129
|
-
}
|
|
130
|
-
else if (/^v?\d+\.\d+\.\d+/.test(to.trim())) {
|
|
131
|
-
version = to.trim().replace(/^v/, "");
|
|
86
|
+
if (to !== undefined && /^v?\d+\.\d+\.\d+/.test(to)) {
|
|
87
|
+
version = to.replace(/^v/, '');
|
|
132
88
|
tag = null;
|
|
133
89
|
}
|
|
134
90
|
else {
|
|
91
|
+
tag = to ?? 'latest';
|
|
135
92
|
const tags = await fetchDistTags(o);
|
|
136
|
-
const resolved = tags[
|
|
93
|
+
const resolved = tags[tag];
|
|
137
94
|
if (resolved === undefined) {
|
|
138
|
-
|
|
95
|
+
const url = registryUrl(o.registry, '-', 'package', o.packageName, 'dist-tags');
|
|
96
|
+
throw new RegistryError(url, `no dist-tag \`${tag}\` (the registry has: ${Object.keys(tags).sort().join(', ')})`);
|
|
139
97
|
}
|
|
140
98
|
version = resolved;
|
|
141
|
-
tag = to;
|
|
142
99
|
}
|
|
143
100
|
const manifest = await fetchManifest({ ...o, version });
|
|
144
101
|
return { version, tag, engines: manifest.engines?.node ?? null };
|