@edspencer/paddock 0.81.0 → 0.82.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.
- package/package.json +1 -1
- package/packages/server/dist/cli/args.d.ts +56 -31
- package/packages/server/dist/cli/args.js +210 -24
- package/packages/server/dist/cli/busy-command.js +1 -1
- package/packages/server/dist/cli/busy.js +10 -12
- 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 +60 -30
- package/packages/server/dist/cli/service/index.d.ts +54 -2
- package/packages/server/dist/cli/service/index.js +198 -25
- package/packages/server/dist/cli/service/launchd.d.ts +31 -84
- package/packages/server/dist/cli/service/launchd.js +104 -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 +30 -0
- package/packages/server/dist/cli/service/ready.js +35 -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 +106 -235
- 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 +84 -0
- package/packages/server/dist/cli/update/install-method.js +183 -0
- package/packages/server/dist/cli/update/lock.d.ts +35 -0
- package/packages/server/dist/cli/update/lock.js +74 -0
- package/packages/server/dist/cli/update/package-manager.d.ts +74 -0
- package/packages/server/dist/cli/update/package-manager.js +156 -0
- package/packages/server/dist/cli/update/registry.d.ts +52 -0
- package/packages/server/dist/cli/update/registry.js +102 -0
- package/packages/server/dist/cli/update/semver.d.ts +45 -0
- package/packages/server/dist/cli/update/semver.js +182 -0
- package/packages/server/dist/cli/update/update-check.d.ts +69 -0
- package/packages/server/dist/cli/update/update-check.js +86 -0
- package/packages/server/dist/cli/update/update.d.ts +126 -0
- package/packages/server/dist/cli/update/update.js +408 -0
- package/packages/server/dist/cli/update-command.d.ts +20 -0
- package/packages/server/dist/cli/update-command.js +111 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-D8oIJ8HC.js → DeleteProjectDialog-CgHpQctt.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-CG6cf-0i.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-o0KCr3aB.js → DiscoverView-BKqI0I1E.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-RSJz7oL3.js → InstanceConfigPage-DL3raCK1.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-39mafH8b.js → MarkdownRenderer-DcjEuf3s.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-Bq7GVguY.js → ProjectRedirect-DerkA-nE.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BoBIru1g.js → ProjectView-NwfC3_w7.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-mJ4gqbr6.js → ProjectsGrid-BeORa0UH.js} +1 -1
- package/packages/web/dist/assets/{RootHome-C0kwFyYp.js → RootHome-BYJs8qJ6.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-MkhXlB8z.js → Surfaces-w-kEaAYU.js} +1 -1
- package/packages/web/dist/assets/{arc-DExlwAwO.js → arc-Bv56hasM.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CZly3W79.js → architectureDiagram-3BPJPVTR-BHEknx9i.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-AgES73i3.js → blockDiagram-GPEHLZMM-DtxTMrPX.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-N9mD8Pfk.js → c4Diagram-AAUBKEIU-DRpEYOlM.js} +1 -1
- package/packages/web/dist/assets/channel-CdIiAqY2.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Dsx_kfUl.js → chunk-2J33WTMH-S4s7GCg7.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-BQjqcjPj.js → chunk-4BX2VUAB-BkWVPLf1.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-CCxpYQpY.js → chunk-55IACEB6-4-FQp01F.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-DpFRdnUa.js → chunk-727SXJPM-BeyDWdIb.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-D2BAlzoO.js → chunk-AQP2D5EJ-ZSxKy745.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-BFaMRzUF.js → chunk-FMBD7UC4-Bq8uV9j-.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-B9NGJ9xZ.js → chunk-ND2GUHAM-BOFsRyHk.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-CF1IHyMS.js → chunk-QZHKN3VN-uZ6oNc4m.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DCCUHba0.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DCCUHba0.js +2 -0
- package/packages/web/dist/assets/{core-XjElsvQ7.js → core-CYuJAM2U.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-CF972Flx.js → cose-bilkent-S5V4N54A-DIzkVSFb.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-DsneI8YE.js → dagre-BM42HDAG-CHR6jJBv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-BHyrVzIk.js → diagram-2AECGRRQ-BAFXCH8Q.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Dj4q8xEi.js → diagram-5GNKFQAL-BJ5t5gbz.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-BHe3Puv_.js → diagram-KO2AKTUF-D9Xt0IVg.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-wzt6G2FE.js → diagram-LMA3HP47-Bq38u6sy.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-CZMbMRJq.js → diagram-OG6HWLK6-CMQVqkpi.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-BwTIA8rI.js → documentTitle-M1lrfXRU.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-Dtu14BL1.js → erDiagram-TEJ5UH35-C63M1g9b.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-T34ql9j0.js → flowDiagram-I6XJVG4X-D3j8ACN7.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DQLOeCXK.js → ganttDiagram-6RSMTGT7-B1Fd1A2Y.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-Bk11zea6.js → gitGraphDiagram-PVQCEYII-CEhZhDdR.js} +1 -1
- package/packages/web/dist/assets/{index-CvaWErFv.js → index-1E9yHciS.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-CsE83lu0.js → infoDiagram-5YYISTIA-Ctquaew6.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DNVgA93n.js → ishikawaDiagram-YF4QCWOH-DV4GvA1R.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CLpSNjM3.js → journeyDiagram-JHISSGLW-C_Cy9waA.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BRzuXcKr.js → kanban-definition-UN3LZRKU-CGr9ctEf.js} +1 -1
- package/packages/web/dist/assets/{linear-BxOawONt.js → linear-D3U8NOkt.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-Dvs1QCcz.js → mermaid.core-BiArdWKV.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-Dmt7pYwF.js → mindmap-definition-RKZ34NQL-CZIpzj8Z.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BHiZaJD-.js → pieDiagram-4H26LBE5-1MaeS3rt.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-U0A1noRE.js → quadrantDiagram-W4KKPZXB-B5pgQ54e.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CZLsr6yi.js → requirementDiagram-4Y6WPE33-BNnxrpGr.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BTlLGILU.js → sankeyDiagram-5OEKKPKP-DsxVH-hM.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DpSS_Gb1.js → sequenceDiagram-3UESZ5HK-Ke9LcFZI.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-D9srE-tV.js → stateDiagram-AJRCARHV-UJzmflND.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-DgUc3HkP.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-BYQ5CKab.js → timeline-definition-PNZ67QCA-BRjNALVI.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-B4W2CtSV.js → vennDiagram-CIIHVFJN-ClMAspxz.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY--XUV7GvD.js → wardley-L42UT6IY-UPv4JEmz.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Bl2VIEGY.js → wardleyDiagram-YWT4CUSO-BgYJbdEx.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-Bj2H9Rcf.js → xychartDiagram-2RQKCTM6-Bfk5HjGx.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-CQlzxsn4.js +0 -2
- package/packages/web/dist/assets/channel-ExsAG7qT.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-si7g9okq.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-si7g9okq.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-AxWqsDh-.js +0 -2
|
@@ -0,0 +1,156 @@
|
|
|
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';
|
|
14
|
+
export const spawnSyncRunner = (cmd, args, opts = {}) => {
|
|
15
|
+
const r = spawnSync(cmd, args, {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
...(opts.env !== undefined ? { env: opts.env } : {}),
|
|
18
|
+
...(opts.shell === true ? { shell: true } : {}),
|
|
19
|
+
...(opts.timeoutMs !== undefined ? { timeout: opts.timeoutMs } : {}),
|
|
20
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
});
|
|
23
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? (r.error ? String(r.error.message) : '') };
|
|
24
|
+
};
|
|
25
|
+
/** How long an install may take. */
|
|
26
|
+
export const INSTALL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
27
|
+
/**
|
|
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.
|
|
31
|
+
*/
|
|
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));
|
|
37
|
+
const dir = path.dirname(execPath);
|
|
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 };
|
|
53
|
+
}
|
|
54
|
+
return platform === 'win32' ? { cmd: 'npm.cmd', pre: [], shell: true } : { cmd: 'npm', pre: [], shell: false };
|
|
55
|
+
}
|
|
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) {
|
|
62
|
+
const run = ctx.spawn ?? spawnSyncRunner;
|
|
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 });
|
|
65
|
+
}
|
|
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) => {
|
|
72
|
+
if (r.status !== 0)
|
|
73
|
+
return undefined;
|
|
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));
|
|
80
|
+
}
|
|
81
|
+
/** `npm config get <key>`, or undefined. */
|
|
82
|
+
export function npmConfigGet(key, ctx = {}) {
|
|
83
|
+
return lastLine(runNpm(['config', 'get', key], ctx, 60_000));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
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.
|
|
89
|
+
*/
|
|
90
|
+
export function isWritable(dir) {
|
|
91
|
+
let probe = dir;
|
|
92
|
+
while (!fs.existsSync(probe)) {
|
|
93
|
+
const parent = path.dirname(probe);
|
|
94
|
+
if (parent === probe)
|
|
95
|
+
return false;
|
|
96
|
+
probe = parent;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(probe, fs.constants.W_OK);
|
|
100
|
+
fs.rmSync(fs.mkdtempSync(path.join(probe, '.update-probe-')), { recursive: true, force: true });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
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.
|
|
110
|
+
*/
|
|
111
|
+
export function globalRootFor(manager, ctx = {}) {
|
|
112
|
+
return manager === 'npm' ? npmGlobalRoot(ctx) : undefined;
|
|
113
|
+
}
|
|
114
|
+
/** The global install command, per SPEC §6's last column. */
|
|
115
|
+
export function installArgs(manager, spec) {
|
|
116
|
+
switch (manager) {
|
|
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] };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Installs `<name>@<version>` with the manager this install came from. pnpm, bun and volta come from the PATH. */
|
|
128
|
+
export function installPackage(o) {
|
|
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);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
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.
|
|
138
|
+
*/
|
|
139
|
+
export function cacheAdd(packageName, version, ctx = {}) {
|
|
140
|
+
return runNpm(['cache', 'add', `${packageName}@${version}`], ctx);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
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.
|
|
146
|
+
*/
|
|
147
|
+
export function cliVersion(cliPath, ctx = {}) {
|
|
148
|
+
const r = (ctx.spawn ?? spawnSyncRunner)(ctx.execPath ?? process.execPath, [cliPath, '--version'], {
|
|
149
|
+
...(ctx.env !== undefined ? { env: ctx.env } : {}),
|
|
150
|
+
timeoutMs: 60_000,
|
|
151
|
+
});
|
|
152
|
+
if (r.status !== 0)
|
|
153
|
+
return undefined;
|
|
154
|
+
const line = r.stdout.trim().split(/\r?\n/)[0]?.trim();
|
|
155
|
+
return line !== undefined && /^\d+\.\d+\.\d+/.test(line) ? line : undefined;
|
|
156
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** SPEC §5: 10 seconds for each request. */
|
|
2
|
+
export declare const REGISTRY_TIMEOUT_MS = 10000;
|
|
3
|
+
export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
4
|
+
export interface RegistryOptions {
|
|
5
|
+
packageName: string;
|
|
6
|
+
/** The base URL; a trailing slash is fine. */
|
|
7
|
+
registry: string;
|
|
8
|
+
fetch?: typeof globalThis.fetch;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
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.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveRegistry(env: NodeJS.ProcessEnv, npmConfigGet?: (key: string) => string | undefined): string;
|
|
17
|
+
/** Joins path parts onto the registry, writing a scoped name's `/` as `%2f` (SPEC §5 step 3). Both routes take it. */
|
|
18
|
+
export declare function registryUrl(registry: string, ...parts: string[]): string;
|
|
19
|
+
/** Anything that went wrong talking to the registry, with the URL, as one reportable error. */
|
|
20
|
+
export declare class RegistryError extends Error {
|
|
21
|
+
name: string;
|
|
22
|
+
readonly url: string;
|
|
23
|
+
constructor(url: string, message: string);
|
|
24
|
+
}
|
|
25
|
+
/** `{ latest: "0.81.0", next: "0.82.0-rc.1" }`. */
|
|
26
|
+
export declare function fetchDistTags(o: RegistryOptions): Promise<Record<string, string>>;
|
|
27
|
+
export interface PackageManifest {
|
|
28
|
+
name?: string;
|
|
29
|
+
version?: string;
|
|
30
|
+
engines?: {
|
|
31
|
+
node?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** One version's manifest, read for `engines`. */
|
|
35
|
+
export declare function fetchManifest(o: RegistryOptions & {
|
|
36
|
+
version: string;
|
|
37
|
+
}): Promise<PackageManifest>;
|
|
38
|
+
export interface ResolvedTarget {
|
|
39
|
+
version: string;
|
|
40
|
+
/** The dist-tag asked for, or null when `--to` named a version. */
|
|
41
|
+
tag: string | null;
|
|
42
|
+
/** The target's `engines.node`. */
|
|
43
|
+
engines: string | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
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".
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveTarget(o: RegistryOptions & {
|
|
51
|
+
to?: string;
|
|
52
|
+
}): Promise<ResolvedTarget>;
|
|
@@ -0,0 +1,102 @@
|
|
|
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. */
|
|
11
|
+
export const REGISTRY_TIMEOUT_MS = 10_000;
|
|
12
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
13
|
+
/**
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveRegistry(env, npmConfigGet) {
|
|
19
|
+
const fromEnv = env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY;
|
|
20
|
+
if (fromEnv !== undefined && fromEnv.trim() !== '')
|
|
21
|
+
return fromEnv.trim();
|
|
22
|
+
const fromNpm = npmConfigGet?.('registry')?.trim();
|
|
23
|
+
if (fromNpm !== undefined && fromNpm !== '' && fromNpm !== 'undefined')
|
|
24
|
+
return fromNpm;
|
|
25
|
+
return DEFAULT_REGISTRY;
|
|
26
|
+
}
|
|
27
|
+
/** Joins path parts onto the registry, writing a scoped name's `/` as `%2f` (SPEC §5 step 3). Both routes take it. */
|
|
28
|
+
export function registryUrl(registry, ...parts) {
|
|
29
|
+
return `${registry.replace(/\/+$/, '')}/${parts.map((p) => p.replace(/\//g, '%2f')).join('/')}`;
|
|
30
|
+
}
|
|
31
|
+
/** Anything that went wrong talking to the registry, with the URL, as one reportable error. */
|
|
32
|
+
export class RegistryError extends Error {
|
|
33
|
+
name = 'RegistryError';
|
|
34
|
+
url;
|
|
35
|
+
constructor(url, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.url = url;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function getJson(url, o) {
|
|
41
|
+
const timeoutMs = o.timeoutMs ?? REGISTRY_TIMEOUT_MS;
|
|
42
|
+
let res;
|
|
43
|
+
try {
|
|
44
|
+
res = await (o.fetch ?? globalThis.fetch)(url, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: 'application/json' } });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
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}`);
|
|
50
|
+
}
|
|
51
|
+
if (res.status === 404)
|
|
52
|
+
throw new RegistryError(url, 'the registry has no such package or version');
|
|
53
|
+
if (!res.ok)
|
|
54
|
+
throw new RegistryError(url, `the registry answered HTTP ${res.status}`);
|
|
55
|
+
try {
|
|
56
|
+
return (await res.json());
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new RegistryError(url, "the registry's answer isn't JSON");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** `{ latest: "0.81.0", next: "0.82.0-rc.1" }`. */
|
|
63
|
+
export async function fetchDistTags(o) {
|
|
64
|
+
const url = registryUrl(o.registry, '-', 'package', o.packageName, 'dist-tags');
|
|
65
|
+
const tags = {};
|
|
66
|
+
for (const [k, v] of Object.entries(await getJson(url, o)))
|
|
67
|
+
if (typeof v === 'string')
|
|
68
|
+
tags[k] = v;
|
|
69
|
+
if (Object.keys(tags).length === 0)
|
|
70
|
+
throw new RegistryError(url, 'the registry lists no dist-tags');
|
|
71
|
+
return tags;
|
|
72
|
+
}
|
|
73
|
+
/** One version's manifest, read for `engines`. */
|
|
74
|
+
export function fetchManifest(o) {
|
|
75
|
+
return getJson(registryUrl(o.registry, o.packageName, o.version), o);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
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".
|
|
81
|
+
*/
|
|
82
|
+
export async function resolveTarget(o) {
|
|
83
|
+
const to = o.to?.trim();
|
|
84
|
+
let version;
|
|
85
|
+
let tag;
|
|
86
|
+
if (to !== undefined && /^v?\d+\.\d+\.\d+/.test(to)) {
|
|
87
|
+
version = to.replace(/^v/, '');
|
|
88
|
+
tag = null;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
tag = to ?? 'latest';
|
|
92
|
+
const tags = await fetchDistTags(o);
|
|
93
|
+
const resolved = tags[tag];
|
|
94
|
+
if (resolved === undefined) {
|
|
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(', ')})`);
|
|
97
|
+
}
|
|
98
|
+
version = resolved;
|
|
99
|
+
}
|
|
100
|
+
const manifest = await fetchManifest({ ...o, version });
|
|
101
|
+
return { version, tag, engines: manifest.engines?.node ?? null };
|
|
102
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface Version {
|
|
2
|
+
major: number;
|
|
3
|
+
minor: number;
|
|
4
|
+
patch: number;
|
|
5
|
+
/** The dot-separated identifiers after `-`; empty for a release. */
|
|
6
|
+
prerelease: (string | number)[];
|
|
7
|
+
}
|
|
8
|
+
/** `1.2.3-rc.1` as a `Version`, or undefined if it isn't one. */
|
|
9
|
+
export declare function parseVersion(raw: string): Version | undefined;
|
|
10
|
+
/** SPEC §5: prereleases count only when `--to` names them. */
|
|
11
|
+
export declare function isPrerelease(raw: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* -1, 0 or 1, by semver's rules: `1.0.0-rc.1` is lower than `1.0.0`, numeric identifiers compare as numbers and sort
|
|
14
|
+
* below alphanumeric ones, and a longer run of identifiers wins when the shared ones are equal. Build metadata is
|
|
15
|
+
* ignored. Something that isn't a version compares as equal rather than throwing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
18
|
+
/** Whether `a` is strictly newer than `b`. */
|
|
19
|
+
export declare const isNewer: (a: string, b: string) => boolean;
|
|
20
|
+
/** The highest of a list, ignoring anything that isn't a version. */
|
|
21
|
+
export declare function maxVersion(versions: readonly string[]): string | undefined;
|
|
22
|
+
/** `0.82.0` → `0.83.0` (`minor`), or `0.82.1` (`patch`). A prerelease's suffix is dropped. */
|
|
23
|
+
export declare function bumpVersion(raw: string, part: 'major' | 'minor' | 'patch'): string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether `version` satisfies `range`; undefined when this file can't tell.
|
|
26
|
+
*
|
|
27
|
+
* Reads what `engines.node` uses in practice: `>=22`, `>=22.0.0`, `^22.1.0`, `~22.1`, `22`, `22.x`, `>=20 <23`, and
|
|
28
|
+
* `||` between them. `*` and an empty range accept everything. Alternatives it can't read are ignored; if it can read
|
|
29
|
+
* none of them, the answer is undefined.
|
|
30
|
+
*/
|
|
31
|
+
export declare function satisfies(version: string, range: string): boolean | undefined;
|
|
32
|
+
export interface EnginesVerdict {
|
|
33
|
+
/** true passes, false fails, undefined when the range couldn't be read. */
|
|
34
|
+
ok: boolean | undefined;
|
|
35
|
+
/** The range as published, for the message. */
|
|
36
|
+
range: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Will the target run on this Node? (SPEC §5 step 4.) No `engines.node`, or one this file can't read, doesn't block
|
|
40
|
+
* an update: callers treat `ok: undefined` as a pass. A false refusal leaves a tool that can't update itself, saying
|
|
41
|
+
* something nobody can act on; letting an unsuitable Node through fails the install, and the rollback runs.
|
|
42
|
+
*/
|
|
43
|
+
export declare function satisfiesEngines(nodeVersion: string, engines: {
|
|
44
|
+
node?: string;
|
|
45
|
+
} | undefined): EnginesVerdict;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// From edspencer/cli-contract reference/semver.ts @ 0.2; local changes: `.ts` import specifiers → `.js`.
|
|
2
|
+
// The small semver `update` needs (SPEC §5 step 4): is the target newer, is it a prerelease, and does its
|
|
3
|
+
// `engines.node` accept this Node. Node built-ins only.
|
|
4
|
+
//
|
|
5
|
+
// It deliberately leaves out hyphen ranges (`1.2 - 2.3`) and build-metadata ordering. A range it can't read is
|
|
6
|
+
// "can't tell" (undefined), not "fails": refusing an update over a range this file doesn't understand would be the
|
|
7
|
+
// worse mistake of the two (`satisfiesEngines`).
|
|
8
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
9
|
+
const identifiers = (s) => s.split('.').map((id) => (/^\d+$/.test(id) ? Number(id) : id));
|
|
10
|
+
/** `1.2.3-rc.1` as a `Version`, or undefined if it isn't one. */
|
|
11
|
+
export function parseVersion(raw) {
|
|
12
|
+
const m = VERSION_RE.exec(raw.trim());
|
|
13
|
+
if (m === null)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), prerelease: m[4] === undefined ? [] : identifiers(m[4]) };
|
|
16
|
+
}
|
|
17
|
+
/** SPEC §5: prereleases count only when `--to` names them. */
|
|
18
|
+
export function isPrerelease(raw) {
|
|
19
|
+
return (parseVersion(raw)?.prerelease.length ?? 0) > 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* -1, 0 or 1, by semver's rules: `1.0.0-rc.1` is lower than `1.0.0`, numeric identifiers compare as numbers and sort
|
|
23
|
+
* below alphanumeric ones, and a longer run of identifiers wins when the shared ones are equal. Build metadata is
|
|
24
|
+
* ignored. Something that isn't a version compares as equal rather than throwing.
|
|
25
|
+
*/
|
|
26
|
+
export function compareVersions(a, b) {
|
|
27
|
+
const va = parseVersion(a);
|
|
28
|
+
const vb = parseVersion(b);
|
|
29
|
+
if (va === undefined || vb === undefined)
|
|
30
|
+
return 0;
|
|
31
|
+
return compare(va, vb);
|
|
32
|
+
}
|
|
33
|
+
function compare(va, vb) {
|
|
34
|
+
for (const k of ['major', 'minor', 'patch']) {
|
|
35
|
+
if (va[k] !== vb[k])
|
|
36
|
+
return va[k] < vb[k] ? -1 : 1;
|
|
37
|
+
}
|
|
38
|
+
if (va.prerelease.length === 0 && vb.prerelease.length === 0)
|
|
39
|
+
return 0;
|
|
40
|
+
if (va.prerelease.length === 0)
|
|
41
|
+
return 1;
|
|
42
|
+
if (vb.prerelease.length === 0)
|
|
43
|
+
return -1;
|
|
44
|
+
const n = Math.max(va.prerelease.length, vb.prerelease.length);
|
|
45
|
+
for (let i = 0; i < n; i++) {
|
|
46
|
+
const x = va.prerelease[i];
|
|
47
|
+
const y = vb.prerelease[i];
|
|
48
|
+
if (x === undefined)
|
|
49
|
+
return -1;
|
|
50
|
+
if (y === undefined)
|
|
51
|
+
return 1;
|
|
52
|
+
if (x === y)
|
|
53
|
+
continue;
|
|
54
|
+
if (typeof x === 'number' && typeof y === 'number')
|
|
55
|
+
return x < y ? -1 : 1;
|
|
56
|
+
if (typeof x === 'number')
|
|
57
|
+
return -1;
|
|
58
|
+
if (typeof y === 'number')
|
|
59
|
+
return 1;
|
|
60
|
+
return x < y ? -1 : 1;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
/** Whether `a` is strictly newer than `b`. */
|
|
65
|
+
export const isNewer = (a, b) => compareVersions(a, b) > 0;
|
|
66
|
+
/** The highest of a list, ignoring anything that isn't a version. */
|
|
67
|
+
export function maxVersion(versions) {
|
|
68
|
+
let best;
|
|
69
|
+
for (const v of versions) {
|
|
70
|
+
if (parseVersion(v) !== undefined && (best === undefined || isNewer(v, best)))
|
|
71
|
+
best = v;
|
|
72
|
+
}
|
|
73
|
+
return best;
|
|
74
|
+
}
|
|
75
|
+
/** `0.82.0` → `0.83.0` (`minor`), or `0.82.1` (`patch`). A prerelease's suffix is dropped. */
|
|
76
|
+
export function bumpVersion(raw, part) {
|
|
77
|
+
const v = parseVersion(raw);
|
|
78
|
+
if (v === undefined)
|
|
79
|
+
throw new Error(`not a version: ${raw}`);
|
|
80
|
+
if (part === 'major')
|
|
81
|
+
return `${v.major + 1}.0.0`;
|
|
82
|
+
if (part === 'minor')
|
|
83
|
+
return `${v.major}.${v.minor + 1}.0`;
|
|
84
|
+
return `${v.major}.${v.minor}.${v.patch + 1}`;
|
|
85
|
+
}
|
|
86
|
+
const COMPARATOR_RE = /^(>=|<=|>|<|=|\^|~)?\s*v?(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?$/;
|
|
87
|
+
/**
|
|
88
|
+
* One range atom as the comparators it means: `^22.1.0` is `>=22.1.0 <23.0.0`, `~22.1.0` is `>=22.1.0 <22.2.0`, and
|
|
89
|
+
* a bare `22` is `>=22.0.0 <23.0.0` (npm's partial versions). Undefined for anything else.
|
|
90
|
+
*/
|
|
91
|
+
function comparatorsFor(atom) {
|
|
92
|
+
const m = COMPARATOR_RE.exec(atom);
|
|
93
|
+
if (m === null)
|
|
94
|
+
return undefined;
|
|
95
|
+
const op = m[1] ?? '=';
|
|
96
|
+
const wild = (s) => s === undefined || s === 'x' || s === 'X' || s === '*';
|
|
97
|
+
const major = Number(m[2]);
|
|
98
|
+
const minorWild = wild(m[3]);
|
|
99
|
+
const patchWild = wild(m[4]);
|
|
100
|
+
const minor = minorWild ? 0 : Number(m[3]);
|
|
101
|
+
const patch = patchWild ? 0 : Number(m[4]);
|
|
102
|
+
const at = (ma, mi, pa) => ({ major: ma, minor: mi, patch: pa, prerelease: [] });
|
|
103
|
+
const lower = { major, minor, patch, prerelease: m[5] === undefined ? [] : identifiers(m[5]) };
|
|
104
|
+
const between = (upper) => [
|
|
105
|
+
{ op: '>=', version: lower },
|
|
106
|
+
{ op: '<', version: upper },
|
|
107
|
+
];
|
|
108
|
+
// `^0.x` tightens: ^0.2.3 is >=0.2.3 <0.3.0, ^0.0.3 is >=0.0.3 <0.0.4, ^0.0 is <0.1.0 and ^0 is <1.0.0.
|
|
109
|
+
if (op === '^') {
|
|
110
|
+
if (major > 0 || minorWild)
|
|
111
|
+
return between(at(major + 1, 0, 0));
|
|
112
|
+
if (minor > 0 || patchWild)
|
|
113
|
+
return between(at(0, minor + 1, 0));
|
|
114
|
+
return between(at(0, 0, patch + 1));
|
|
115
|
+
}
|
|
116
|
+
if (op === '~')
|
|
117
|
+
return between(minorWild ? at(major + 1, 0, 0) : at(major, minor + 1, 0));
|
|
118
|
+
// A partial version with no operator is a range: `22` is the whole 22 line, `22.1` the whole 22.1 line.
|
|
119
|
+
if (op === '=' && (minorWild || patchWild))
|
|
120
|
+
return between(minorWild ? at(major + 1, 0, 0) : at(major, minor + 1, 0));
|
|
121
|
+
return [{ op: op, version: lower }];
|
|
122
|
+
}
|
|
123
|
+
function satisfiesComparator(v, c) {
|
|
124
|
+
const cmp = compare(v, c.version);
|
|
125
|
+
switch (c.op) {
|
|
126
|
+
case '>=':
|
|
127
|
+
return cmp >= 0;
|
|
128
|
+
case '<=':
|
|
129
|
+
return cmp <= 0;
|
|
130
|
+
case '>':
|
|
131
|
+
return cmp > 0;
|
|
132
|
+
case '<':
|
|
133
|
+
return cmp < 0;
|
|
134
|
+
case '=':
|
|
135
|
+
return cmp === 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether `version` satisfies `range`; undefined when this file can't tell.
|
|
140
|
+
*
|
|
141
|
+
* Reads what `engines.node` uses in practice: `>=22`, `>=22.0.0`, `^22.1.0`, `~22.1`, `22`, `22.x`, `>=20 <23`, and
|
|
142
|
+
* `||` between them. `*` and an empty range accept everything. Alternatives it can't read are ignored; if it can read
|
|
143
|
+
* none of them, the answer is undefined.
|
|
144
|
+
*/
|
|
145
|
+
export function satisfies(version, range) {
|
|
146
|
+
const v = parseVersion(version);
|
|
147
|
+
if (v === undefined)
|
|
148
|
+
return undefined;
|
|
149
|
+
const trimmed = range.trim();
|
|
150
|
+
if (trimmed === '' || trimmed === '*' || trimmed === 'x')
|
|
151
|
+
return true;
|
|
152
|
+
let known = false;
|
|
153
|
+
for (const alt of trimmed.split('||')) {
|
|
154
|
+
const comparators = [];
|
|
155
|
+
let parsed = true;
|
|
156
|
+
for (const atom of alt.trim().split(/\s+/).filter(Boolean)) {
|
|
157
|
+
const cs = comparatorsFor(atom);
|
|
158
|
+
if (cs === undefined) {
|
|
159
|
+
parsed = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
comparators.push(...cs);
|
|
163
|
+
}
|
|
164
|
+
if (!parsed)
|
|
165
|
+
continue;
|
|
166
|
+
known = true;
|
|
167
|
+
if (comparators.every((c) => satisfiesComparator(v, c)))
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return known ? false : undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Will the target run on this Node? (SPEC §5 step 4.) No `engines.node`, or one this file can't read, doesn't block
|
|
174
|
+
* an update: callers treat `ok: undefined` as a pass. A false refusal leaves a tool that can't update itself, saying
|
|
175
|
+
* something nobody can act on; letting an unsuitable Node through fails the install, and the rollback runs.
|
|
176
|
+
*/
|
|
177
|
+
export function satisfiesEngines(nodeVersion, engines) {
|
|
178
|
+
const range = engines?.node;
|
|
179
|
+
if (range === undefined || range.trim() === '')
|
|
180
|
+
return { ok: true, range: undefined };
|
|
181
|
+
return { ok: satisfies(nodeVersion, range), range };
|
|
182
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { InstallMethod } from './install-method.js';
|
|
2
|
+
export declare const UPDATE_CHECK_FILE = "update-check.json";
|
|
3
|
+
export declare function updateCheckPath(home: string): string;
|
|
4
|
+
/** `update --check --json`, and the cached copy. */
|
|
5
|
+
export interface UpdateCheck {
|
|
6
|
+
name: string;
|
|
7
|
+
current: string;
|
|
8
|
+
target: string;
|
|
9
|
+
/** The dist-tag asked for, or null when `--to` named a version. */
|
|
10
|
+
tag: string | null;
|
|
11
|
+
updateAvailable: boolean;
|
|
12
|
+
installMethod: InstallMethod;
|
|
13
|
+
canUpdate: boolean;
|
|
14
|
+
/** `<tool> update`, or the command this install needs instead; null once there's nothing to do. */
|
|
15
|
+
command: string | null;
|
|
16
|
+
engines: string | null;
|
|
17
|
+
nodeOk: boolean;
|
|
18
|
+
changelog: string | null;
|
|
19
|
+
/** ISO 8601. */
|
|
20
|
+
checkedAt: string;
|
|
21
|
+
}
|
|
22
|
+
/** `service status --json`'s `update` (`schemas/service-status.schema.json`). */
|
|
23
|
+
export interface UpdateSummary {
|
|
24
|
+
latest: string;
|
|
25
|
+
updateAvailable: boolean;
|
|
26
|
+
checkedAt: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Writes the cache atomically, so `status` never reads half a file and says nothing. It never throws: a cache that
|
|
30
|
+
* couldn't be written isn't a failed update.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeUpdateCheck(home: string, check: UpdateCheck): void;
|
|
33
|
+
/** The cache, or null when there isn't one (or it isn't one). */
|
|
34
|
+
export declare function readUpdateCheck(home: string): UpdateCheck | null;
|
|
35
|
+
/**
|
|
36
|
+
* How long a cached answer is worth repeating: long enough for "I checked on Monday, it's Friday", short enough that
|
|
37
|
+
* `status` doesn't advertise a version that's since been superseded twice.
|
|
38
|
+
*/
|
|
39
|
+
export declare const UPDATE_CHECK_MAX_AGE_MS: number;
|
|
40
|
+
/**
|
|
41
|
+
* What `service status` repeats, or undefined when there's nothing worth repeating: no cache, a cache for another
|
|
42
|
+
* version than this one, or one that's too old. Leave `update` out of `status --json` then: the schema types it as an
|
|
43
|
+
* object, so it's absent, never null.
|
|
44
|
+
*/
|
|
45
|
+
export declare function updateSummary(home: string, o?: {
|
|
46
|
+
now?: number;
|
|
47
|
+
maxAgeMs?: number;
|
|
48
|
+
currentVersion?: string;
|
|
49
|
+
}): UpdateSummary | undefined;
|
|
50
|
+
export interface NoticeInput {
|
|
51
|
+
name: string;
|
|
52
|
+
current: string;
|
|
53
|
+
target: string;
|
|
54
|
+
/** `<tool> update`, or the command this install needs instead. */
|
|
55
|
+
command: string;
|
|
56
|
+
/** What goes in brackets after it: `the global folder isn't writable`. */
|
|
57
|
+
note?: string;
|
|
58
|
+
changelog?: string | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* SPEC §3's notice, for the caller to put on stderr:
|
|
62
|
+
*
|
|
63
|
+
* ```
|
|
64
|
+
* Paddock 0.80.0 is available (you have 0.79.1).
|
|
65
|
+
* Run: paddock update
|
|
66
|
+
* Changes: https://github.com/edspencer/paddock/releases/tag/v0.80.0
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function updateNotice(o: NoticeInput): string;
|