@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,67 +1,44 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The small semver `update` needs, and no more (CLI contract SPEC §5 step 4).
|
|
3
|
-
*
|
|
4
|
-
* Three questions are asked of versions in this contract: is the target newer
|
|
5
|
-
* than what we have, is it a prerelease, and does the target's `engines.node`
|
|
6
|
-
* accept the Node running the update. That is it — so this is ~120 lines of
|
|
7
|
-
* Node built-ins rather than a dependency, which is what SPEC §5 asks for and
|
|
8
|
-
* what keeps `update` runnable from a CLI that must not load the server.
|
|
9
|
-
*
|
|
10
|
-
* **What it deliberately does not do.** No `semver` package behaviours beyond
|
|
11
|
-
* the above: no ranges with hyphens (`1.2 - 2.3`), no `x`-ranges (`1.x`), no
|
|
12
|
-
* build metadata ordering. An `engines.node` we cannot parse is reported as
|
|
13
|
-
* "cannot tell" rather than as "fails", because refusing an update on the
|
|
14
|
-
* strength of a range this file does not understand would be the worse error of
|
|
15
|
-
* the two — see {@link satisfiesEngines}.
|
|
16
|
-
*
|
|
17
|
-
* Generic: wave C4 lifts this into `reference/` with the rest of `update`.
|
|
18
|
-
*/
|
|
19
1
|
export interface Version {
|
|
20
2
|
major: number;
|
|
21
3
|
minor: number;
|
|
22
4
|
patch: number;
|
|
23
|
-
/** The dot-separated identifiers after
|
|
5
|
+
/** The dot-separated identifiers after `-`; empty for a release. */
|
|
24
6
|
prerelease: (string | number)[];
|
|
25
7
|
}
|
|
26
|
-
/** `1.2.3-rc.1`
|
|
8
|
+
/** `1.2.3-rc.1` as a `Version`, or undefined if it isn't one. */
|
|
27
9
|
export declare function parseVersion(raw: string): Version | undefined;
|
|
28
|
-
/**
|
|
10
|
+
/** SPEC §5: prereleases count only when `--to` names them. */
|
|
29
11
|
export declare function isPrerelease(raw: string): boolean;
|
|
30
12
|
/**
|
|
31
|
-
* -1, 0 or 1, by
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* longer run of identifiers wins when every shared one is equal.
|
|
35
|
-
*
|
|
36
|
-
* Build metadata is ignored, as semver says it must be.
|
|
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.
|
|
37
16
|
*/
|
|
38
17
|
export declare function compareVersions(a: string, b: string): number;
|
|
39
|
-
/**
|
|
18
|
+
/** Whether `a` is strictly newer than `b`. */
|
|
40
19
|
export declare const isNewer: (a: string, b: string) => boolean;
|
|
41
|
-
/** The highest of a list, ignoring anything
|
|
20
|
+
/** The highest of a list, ignoring anything that isn't a version. */
|
|
42
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;
|
|
43
24
|
/**
|
|
44
|
-
*
|
|
25
|
+
* Whether `version` satisfies `range`; undefined when this file can't tell.
|
|
45
26
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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.
|
|
49
30
|
*/
|
|
50
31
|
export declare function satisfies(version: string, range: string): boolean | undefined;
|
|
51
32
|
export interface EnginesVerdict {
|
|
52
|
-
/**
|
|
33
|
+
/** true passes, false fails, undefined when the range couldn't be read. */
|
|
53
34
|
ok: boolean | undefined;
|
|
54
35
|
/** The range as published, for the message. */
|
|
55
36
|
range: string | undefined;
|
|
56
37
|
}
|
|
57
38
|
/**
|
|
58
|
-
* Will the target
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* one whose range this file cannot parse, must NOT block an update: the cost of
|
|
62
|
-
* a false refusal is a tool that cannot update itself and says something the
|
|
63
|
-
* user cannot act on, while the cost of letting an incompatible Node through is
|
|
64
|
-
* the failure `engines` would have produced anyway, with a rollback behind it.
|
|
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.
|
|
65
42
|
*/
|
|
66
43
|
export declare function satisfiesEngines(nodeVersion: string, engines: {
|
|
67
44
|
node?: string;
|
|
@@ -1,53 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* what keeps `update` runnable from a CLI that must not load the server.
|
|
9
|
-
*
|
|
10
|
-
* **What it deliberately does not do.** No `semver` package behaviours beyond
|
|
11
|
-
* the above: no ranges with hyphens (`1.2 - 2.3`), no `x`-ranges (`1.x`), no
|
|
12
|
-
* build metadata ordering. An `engines.node` we cannot parse is reported as
|
|
13
|
-
* "cannot tell" rather than as "fails", because refusing an update on the
|
|
14
|
-
* strength of a range this file does not understand would be the worse error of
|
|
15
|
-
* the two — see {@link satisfiesEngines}.
|
|
16
|
-
*
|
|
17
|
-
* Generic: wave C4 lifts this into `reference/` with the rest of `update`.
|
|
18
|
-
*/
|
|
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`).
|
|
19
8
|
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
20
|
-
|
|
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. */
|
|
21
11
|
export function parseVersion(raw) {
|
|
22
12
|
const m = VERSION_RE.exec(raw.trim());
|
|
23
13
|
if (m === null)
|
|
24
14
|
return undefined;
|
|
25
|
-
|
|
26
|
-
? []
|
|
27
|
-
: m[4].split(".").map((id) => (/^\d+$/.test(id) ? Number(id) : id));
|
|
28
|
-
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), prerelease };
|
|
15
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), prerelease: m[4] === undefined ? [] : identifiers(m[4]) };
|
|
29
16
|
}
|
|
30
|
-
/**
|
|
17
|
+
/** SPEC §5: prereleases count only when `--to` names them. */
|
|
31
18
|
export function isPrerelease(raw) {
|
|
32
19
|
return (parseVersion(raw)?.prerelease.length ?? 0) > 0;
|
|
33
20
|
}
|
|
34
21
|
/**
|
|
35
|
-
* -1, 0 or 1, by
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* longer run of identifiers wins when every shared one is equal.
|
|
39
|
-
*
|
|
40
|
-
* Build metadata is ignored, as semver says it must be.
|
|
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.
|
|
41
25
|
*/
|
|
42
26
|
export function compareVersions(a, b) {
|
|
43
27
|
const va = parseVersion(a);
|
|
44
28
|
const vb = parseVersion(b);
|
|
45
|
-
// Unparseable compares as equal rather than throwing: the callers all have a
|
|
46
|
-
// better answer for "I cannot tell" than a crash (`update` says the registry
|
|
47
|
-
// gave it something it did not understand, and stops).
|
|
48
29
|
if (va === undefined || vb === undefined)
|
|
49
30
|
return 0;
|
|
50
|
-
|
|
31
|
+
return compare(va, vb);
|
|
32
|
+
}
|
|
33
|
+
function compare(va, vb) {
|
|
34
|
+
for (const k of ['major', 'minor', 'patch']) {
|
|
51
35
|
if (va[k] !== vb[k])
|
|
52
36
|
return va[k] < vb[k] ? -1 : 1;
|
|
53
37
|
}
|
|
@@ -67,137 +51,109 @@ export function compareVersions(a, b) {
|
|
|
67
51
|
return 1;
|
|
68
52
|
if (x === y)
|
|
69
53
|
continue;
|
|
70
|
-
|
|
71
|
-
const yNum = typeof y === "number";
|
|
72
|
-
if (xNum && yNum)
|
|
54
|
+
if (typeof x === 'number' && typeof y === 'number')
|
|
73
55
|
return x < y ? -1 : 1;
|
|
74
|
-
if (
|
|
56
|
+
if (typeof x === 'number')
|
|
75
57
|
return -1;
|
|
76
|
-
if (
|
|
58
|
+
if (typeof y === 'number')
|
|
77
59
|
return 1;
|
|
78
|
-
return
|
|
60
|
+
return x < y ? -1 : 1;
|
|
79
61
|
}
|
|
80
62
|
return 0;
|
|
81
63
|
}
|
|
82
|
-
/**
|
|
64
|
+
/** Whether `a` is strictly newer than `b`. */
|
|
83
65
|
export const isNewer = (a, b) => compareVersions(a, b) > 0;
|
|
84
|
-
/** The highest of a list, ignoring anything
|
|
66
|
+
/** The highest of a list, ignoring anything that isn't a version. */
|
|
85
67
|
export function maxVersion(versions) {
|
|
86
68
|
let best;
|
|
87
69
|
for (const v of versions) {
|
|
88
|
-
if (parseVersion(v) === undefined)
|
|
89
|
-
continue;
|
|
90
|
-
if (best === undefined || isNewer(v, best))
|
|
70
|
+
if (parseVersion(v) !== undefined && (best === undefined || isNewer(v, best)))
|
|
91
71
|
best = v;
|
|
92
72
|
}
|
|
93
73
|
return best;
|
|
94
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
|
+
}
|
|
95
86
|
const COMPARATOR_RE = /^(>=|<=|>|<|=|\^|~)?\s*v?(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?$/;
|
|
96
87
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* `^22.1.0` is `>=22.1.0 <23.0.0`; `~22.1.0` is `>=22.1.0 <22.2.0`; a bare
|
|
100
|
-
* `22` is `>=22.0.0 <23.0.0` (npm's partial-version semantics, which is what
|
|
101
|
-
* `engines: { node: "22" }` means in the wild). Returns undefined for anything
|
|
102
|
-
* this file does not understand, which propagates up as "cannot tell".
|
|
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.
|
|
103
90
|
*/
|
|
104
91
|
function comparatorsFor(atom) {
|
|
105
92
|
const m = COMPARATOR_RE.exec(atom);
|
|
106
93
|
if (m === null)
|
|
107
94
|
return undefined;
|
|
108
|
-
const op = m[1] ??
|
|
95
|
+
const op = m[1] ?? '=';
|
|
96
|
+
const wild = (s) => s === undefined || s === 'x' || s === 'X' || s === '*';
|
|
109
97
|
const major = Number(m[2]);
|
|
110
|
-
const wild = (s) => s === undefined || s === "x" || s === "X" || s === "*";
|
|
111
98
|
const minorWild = wild(m[3]);
|
|
112
99
|
const patchWild = wild(m[4]);
|
|
113
100
|
const minor = minorWild ? 0 : Number(m[3]);
|
|
114
101
|
const patch = patchWild ? 0 : Number(m[4]);
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? at(0, minor + 1, 0)
|
|
129
|
-
: at(1, 0, 0);
|
|
130
|
-
return [
|
|
131
|
-
{ op: ">=", version: lower },
|
|
132
|
-
{ op: "<", version: upper },
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
if (op === "~") {
|
|
136
|
-
const upper = minorWild ? at(major + 1, 0, 0) : at(major, minor + 1, 0);
|
|
137
|
-
return [
|
|
138
|
-
{ op: ">=", version: lower },
|
|
139
|
-
{ op: "<", version: upper },
|
|
140
|
-
];
|
|
141
|
-
}
|
|
142
|
-
if (op === "=" && (minorWild || patchWild)) {
|
|
143
|
-
// A partial version with no operator is a range, not an equality: `22`
|
|
144
|
-
// means the whole 22 line, and `22.1` means the whole 22.1 line.
|
|
145
|
-
const upper = minorWild ? at(major + 1, 0, 0) : at(major, minor + 1, 0);
|
|
146
|
-
return [
|
|
147
|
-
{ op: ">=", version: lower },
|
|
148
|
-
{ op: "<", version: upper },
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
if (minorWild || patchWild) {
|
|
152
|
-
// `>=22` / `<23`: fill the missing parts with zero, which is what the
|
|
153
|
-
// comparison wants in both directions.
|
|
154
|
-
return [{ op: op, version: lower }];
|
|
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));
|
|
155
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));
|
|
156
121
|
return [{ op: op, version: lower }];
|
|
157
122
|
}
|
|
158
123
|
function satisfiesComparator(v, c) {
|
|
159
124
|
const cmp = compare(v, c.version);
|
|
160
125
|
switch (c.op) {
|
|
161
|
-
case
|
|
126
|
+
case '>=':
|
|
162
127
|
return cmp >= 0;
|
|
163
|
-
case
|
|
128
|
+
case '<=':
|
|
164
129
|
return cmp <= 0;
|
|
165
|
-
case
|
|
130
|
+
case '>':
|
|
166
131
|
return cmp > 0;
|
|
167
|
-
case
|
|
132
|
+
case '<':
|
|
168
133
|
return cmp < 0;
|
|
169
|
-
case
|
|
134
|
+
case '=':
|
|
170
135
|
return cmp === 0;
|
|
171
136
|
}
|
|
172
137
|
}
|
|
173
|
-
function compare(a, b) {
|
|
174
|
-
return compareVersions(stringify(a), stringify(b));
|
|
175
|
-
}
|
|
176
|
-
function stringify(v) {
|
|
177
|
-
const base = `${v.major}.${v.minor}.${v.patch}`;
|
|
178
|
-
return v.prerelease.length === 0 ? base : `${base}-${v.prerelease.join(".")}`;
|
|
179
|
-
}
|
|
180
138
|
/**
|
|
181
|
-
*
|
|
139
|
+
* Whether `version` satisfies `range`; undefined when this file can't tell.
|
|
182
140
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
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.
|
|
186
144
|
*/
|
|
187
145
|
export function satisfies(version, range) {
|
|
188
146
|
const v = parseVersion(version);
|
|
189
147
|
if (v === undefined)
|
|
190
148
|
return undefined;
|
|
191
149
|
const trimmed = range.trim();
|
|
192
|
-
if (trimmed ===
|
|
150
|
+
if (trimmed === '' || trimmed === '*' || trimmed === 'x')
|
|
193
151
|
return true;
|
|
194
|
-
const alternatives = trimmed.split("||");
|
|
195
152
|
let known = false;
|
|
196
|
-
for (const alt of
|
|
197
|
-
const atoms = alt.trim().split(/\s+/).filter(Boolean);
|
|
153
|
+
for (const alt of trimmed.split('||')) {
|
|
198
154
|
const comparators = [];
|
|
199
155
|
let parsed = true;
|
|
200
|
-
for (const atom of
|
|
156
|
+
for (const atom of alt.trim().split(/\s+/).filter(Boolean)) {
|
|
201
157
|
const cs = comparatorsFor(atom);
|
|
202
158
|
if (cs === undefined) {
|
|
203
159
|
parsed = false;
|
|
@@ -211,22 +167,16 @@ export function satisfies(version, range) {
|
|
|
211
167
|
if (comparators.every((c) => satisfiesComparator(v, c)))
|
|
212
168
|
return true;
|
|
213
169
|
}
|
|
214
|
-
// Every alternative we could read said no. If we could read none of them, say
|
|
215
|
-
// so rather than claiming a refusal.
|
|
216
170
|
return known ? false : undefined;
|
|
217
171
|
}
|
|
218
172
|
/**
|
|
219
|
-
* Will the target
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* one whose range this file cannot parse, must NOT block an update: the cost of
|
|
223
|
-
* a false refusal is a tool that cannot update itself and says something the
|
|
224
|
-
* user cannot act on, while the cost of letting an incompatible Node through is
|
|
225
|
-
* the failure `engines` would have produced anyway, with a rollback behind it.
|
|
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.
|
|
226
176
|
*/
|
|
227
177
|
export function satisfiesEngines(nodeVersion, engines) {
|
|
228
178
|
const range = engines?.node;
|
|
229
|
-
if (range === undefined || range.trim() ===
|
|
179
|
+
if (range === undefined || range.trim() === '')
|
|
230
180
|
return { ok: true, range: undefined };
|
|
231
181
|
return { ok: satisfies(nodeVersion, range), range };
|
|
232
182
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { InstallMethod } from
|
|
1
|
+
import type { InstallMethod } from './install-method.js';
|
|
2
2
|
export declare const UPDATE_CHECK_FILE = "update-check.json";
|
|
3
3
|
export declare function updateCheckPath(home: string): string;
|
|
4
|
-
/** `update --check --json`, and the cached copy.
|
|
4
|
+
/** `update --check --json`, and the cached copy. */
|
|
5
5
|
export interface UpdateCheck {
|
|
6
6
|
name: string;
|
|
7
7
|
current: string;
|
|
@@ -11,7 +11,7 @@ export interface UpdateCheck {
|
|
|
11
11
|
updateAvailable: boolean;
|
|
12
12
|
installMethod: InstallMethod;
|
|
13
13
|
canUpdate: boolean;
|
|
14
|
-
/** `<tool> update`, or the command this install needs instead. */
|
|
14
|
+
/** `<tool> update`, or the command this install needs instead; null once there's nothing to do. */
|
|
15
15
|
command: string | null;
|
|
16
16
|
engines: string | null;
|
|
17
17
|
nodeOk: boolean;
|
|
@@ -19,62 +19,51 @@ export interface UpdateCheck {
|
|
|
19
19
|
/** ISO 8601. */
|
|
20
20
|
checkedAt: string;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/** `service status --json`'s `update` (`schemas/service-status.schema.json`). */
|
|
23
23
|
export interface UpdateSummary {
|
|
24
24
|
latest: string;
|
|
25
25
|
updateAvailable: boolean;
|
|
26
26
|
checkedAt: string;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* Atomic because `service status` reads it on a path where a torn read has no
|
|
32
|
-
* recovery worth having — it would report `updateAvailable` as absent and say
|
|
33
|
-
* nothing, which is indistinguishable from "never checked". Best-effort:
|
|
34
|
-
* failing to write a cache must never fail an update, so this throws nothing.
|
|
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.
|
|
35
31
|
*/
|
|
36
32
|
export declare function writeUpdateCheck(home: string, check: UpdateCheck): void;
|
|
37
33
|
/** The cache, or null when there isn't one (or it isn't one). */
|
|
38
34
|
export declare function readUpdateCheck(home: string): UpdateCheck | null;
|
|
39
35
|
/**
|
|
40
|
-
* How long a cached answer is worth repeating
|
|
41
|
-
*
|
|
42
|
-
* `status` says "0.81.0 is available" on the strength of a file, so the file
|
|
43
|
-
* has to be recent enough that the claim is still likely true. A week is long
|
|
44
|
-
* enough to cover "I checked on Monday, it is Friday" and short enough that a
|
|
45
|
-
* months-old file does not have `status` advertising a version that has since
|
|
46
|
-
* been superseded twice.
|
|
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.
|
|
47
38
|
*/
|
|
48
39
|
export declare const UPDATE_CHECK_MAX_AGE_MS: number;
|
|
49
40
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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.
|
|
52
44
|
*/
|
|
53
45
|
export declare function updateSummary(home: string, o?: {
|
|
54
46
|
now?: number;
|
|
55
47
|
maxAgeMs?: number;
|
|
48
|
+
currentVersion?: string;
|
|
56
49
|
}): UpdateSummary | undefined;
|
|
57
50
|
export interface NoticeInput {
|
|
58
51
|
name: string;
|
|
59
52
|
current: string;
|
|
60
53
|
target: string;
|
|
61
|
-
/**
|
|
54
|
+
/** `<tool> update`, or the command this install needs instead. */
|
|
62
55
|
command: string;
|
|
63
|
-
/**
|
|
56
|
+
/** What goes in brackets after it: `the global folder isn't writable`. */
|
|
64
57
|
note?: string;
|
|
65
58
|
changelog?: string | null;
|
|
66
59
|
}
|
|
67
60
|
/**
|
|
68
|
-
* SPEC §3's
|
|
61
|
+
* SPEC §3's notice, for the caller to put on stderr:
|
|
69
62
|
*
|
|
70
63
|
* ```
|
|
71
64
|
* Paddock 0.80.0 is available (you have 0.79.1).
|
|
72
65
|
* Run: paddock update
|
|
73
66
|
* Changes: https://github.com/edspencer/paddock/releases/tag/v0.80.0
|
|
74
67
|
* ```
|
|
75
|
-
*
|
|
76
|
-
* Returned as a string rather than printed, because the caller decides the
|
|
77
|
-
* stream: §3 puts this on **stderr**, always, so that a `--json` document on
|
|
78
|
-
* stdout stays the only thing on stdout.
|
|
79
68
|
*/
|
|
80
69
|
export declare function updateNotice(o: NoticeInput): string;
|
|
@@ -1,33 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* commands that ask write what they found here, and `status` reads the file and
|
|
10
|
-
* never opens a socket.
|
|
11
|
-
*
|
|
12
|
-
* The document is the one `schemas/update-check.schema.json` describes, so
|
|
13
|
-
* `update --check --json` and this file are the same shape — one less thing to
|
|
14
|
-
* keep in step, and `status --json`'s `update` object is a projection of it.
|
|
15
|
-
*
|
|
16
|
-
* Generic: wave C4 lifts this into `reference/`. Node built-ins only.
|
|
17
|
-
*/
|
|
18
|
-
import fs from "node:fs";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
export const UPDATE_CHECK_FILE = "update-check.json";
|
|
1
|
+
// From edspencer/cli-contract reference/update-check.ts @ 0.2; local changes: `.ts` import specifiers → `.js`, and `exit-codes`, `output` and `runtime-file` imported from where Paddock keeps them (`../`, `../../`).
|
|
2
|
+
// `<home>/update-check.json`: what the last registry question found (SPEC §3). Only `update`, `update --check` and
|
|
3
|
+
// `doctor` ask the registry; they write the answer here, and `service status` repeats it without opening a socket.
|
|
4
|
+
// It's the document `update --check --json` prints (`schemas/update-check.schema.json`), and `service status --json`'s
|
|
5
|
+
// `update` is a projection of it. Node built-ins only.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
export const UPDATE_CHECK_FILE = 'update-check.json';
|
|
21
9
|
export function updateCheckPath(home) {
|
|
22
10
|
return path.join(home, UPDATE_CHECK_FILE);
|
|
23
11
|
}
|
|
24
12
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Atomic because `service status` reads it on a path where a torn read has no
|
|
28
|
-
* recovery worth having — it would report `updateAvailable` as absent and say
|
|
29
|
-
* nothing, which is indistinguishable from "never checked". Best-effort:
|
|
30
|
-
* failing to write a cache must never fail an update, so this throws nothing.
|
|
13
|
+
* Writes the cache atomically, so `status` never reads half a file and says nothing. It never throws: a cache that
|
|
14
|
+
* couldn't be written isn't a failed update.
|
|
31
15
|
*/
|
|
32
16
|
export function writeUpdateCheck(home, check) {
|
|
33
17
|
try {
|
|
@@ -44,73 +28,59 @@ export function writeUpdateCheck(home, check) {
|
|
|
44
28
|
}
|
|
45
29
|
}
|
|
46
30
|
catch {
|
|
47
|
-
|
|
31
|
+
// best effort
|
|
48
32
|
}
|
|
49
33
|
}
|
|
50
34
|
/** The cache, or null when there isn't one (or it isn't one). */
|
|
51
35
|
export function readUpdateCheck(home) {
|
|
52
36
|
try {
|
|
53
|
-
const raw = JSON.parse(fs.readFileSync(updateCheckPath(home),
|
|
54
|
-
if (typeof raw.name ===
|
|
55
|
-
typeof raw.current ===
|
|
56
|
-
typeof raw.target ===
|
|
57
|
-
typeof raw.updateAvailable ===
|
|
58
|
-
typeof raw.checkedAt ===
|
|
37
|
+
const raw = JSON.parse(fs.readFileSync(updateCheckPath(home), 'utf8'));
|
|
38
|
+
if (typeof raw.name === 'string' &&
|
|
39
|
+
typeof raw.current === 'string' &&
|
|
40
|
+
typeof raw.target === 'string' &&
|
|
41
|
+
typeof raw.updateAvailable === 'boolean' &&
|
|
42
|
+
typeof raw.checkedAt === 'string') {
|
|
59
43
|
return raw;
|
|
60
44
|
}
|
|
61
45
|
}
|
|
62
46
|
catch {
|
|
63
|
-
|
|
47
|
+
// missing, half-written, or something else's
|
|
64
48
|
}
|
|
65
49
|
return null;
|
|
66
50
|
}
|
|
67
51
|
/**
|
|
68
|
-
* How long a cached answer is worth repeating
|
|
69
|
-
*
|
|
70
|
-
* `status` says "0.81.0 is available" on the strength of a file, so the file
|
|
71
|
-
* has to be recent enough that the claim is still likely true. A week is long
|
|
72
|
-
* enough to cover "I checked on Monday, it is Friday" and short enough that a
|
|
73
|
-
* months-old file does not have `status` advertising a version that has since
|
|
74
|
-
* been superseded twice.
|
|
52
|
+
* How long a cached answer is worth repeating: long enough for "I checked on Monday, it's Friday", short enough that
|
|
53
|
+
* `status` doesn't advertise a version that's since been superseded twice.
|
|
75
54
|
*/
|
|
76
55
|
export const UPDATE_CHECK_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
77
56
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
57
|
+
* What `service status` repeats, or undefined when there's nothing worth repeating: no cache, a cache for another
|
|
58
|
+
* version than this one, or one that's too old. Leave `update` out of `status --json` then: the schema types it as an
|
|
59
|
+
* object, so it's absent, never null.
|
|
80
60
|
*/
|
|
81
61
|
export function updateSummary(home, o = {}) {
|
|
82
62
|
const check = readUpdateCheck(home);
|
|
83
63
|
if (check === null)
|
|
84
64
|
return undefined;
|
|
85
|
-
|
|
86
|
-
if (!Number.isFinite(at))
|
|
65
|
+
if (o.currentVersion !== undefined && check.current !== o.currentVersion)
|
|
87
66
|
return undefined;
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
67
|
+
const at = Date.parse(check.checkedAt);
|
|
68
|
+
if (!Number.isFinite(at) || (o.now ?? Date.now()) - at > (o.maxAgeMs ?? UPDATE_CHECK_MAX_AGE_MS))
|
|
90
69
|
return undefined;
|
|
91
|
-
return {
|
|
92
|
-
latest: check.target,
|
|
93
|
-
updateAvailable: check.updateAvailable,
|
|
94
|
-
checkedAt: check.checkedAt,
|
|
95
|
-
};
|
|
70
|
+
return { latest: check.target, updateAvailable: check.updateAvailable, checkedAt: check.checkedAt };
|
|
96
71
|
}
|
|
97
72
|
/**
|
|
98
|
-
* SPEC §3's
|
|
73
|
+
* SPEC §3's notice, for the caller to put on stderr:
|
|
99
74
|
*
|
|
100
75
|
* ```
|
|
101
76
|
* Paddock 0.80.0 is available (you have 0.79.1).
|
|
102
77
|
* Run: paddock update
|
|
103
78
|
* Changes: https://github.com/edspencer/paddock/releases/tag/v0.80.0
|
|
104
79
|
* ```
|
|
105
|
-
*
|
|
106
|
-
* Returned as a string rather than printed, because the caller decides the
|
|
107
|
-
* stream: §3 puts this on **stderr**, always, so that a `--json` document on
|
|
108
|
-
* stdout stays the only thing on stdout.
|
|
109
80
|
*/
|
|
110
81
|
export function updateNotice(o) {
|
|
111
|
-
const lines = [`${o.name} ${o.target} is available (you have ${o.current})
|
|
112
|
-
lines.push(` Run: ${o.command}${o.note !== undefined ? ` (${o.note})` : ""}`);
|
|
82
|
+
const lines = [`${o.name} ${o.target} is available (you have ${o.current}).`, ` Run: ${o.command}${o.note !== undefined ? ` (${o.note})` : ''}`];
|
|
113
83
|
if (o.changelog !== undefined && o.changelog !== null)
|
|
114
84
|
lines.push(` Changes: ${o.changelog}`);
|
|
115
|
-
return lines.join(
|
|
85
|
+
return lines.join('\n');
|
|
116
86
|
}
|