@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.
Files changed (114) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/cli/args.d.ts +29 -4
  3. package/packages/server/dist/cli/args.js +145 -7
  4. package/packages/server/dist/cli/busy-command.js +1 -1
  5. package/packages/server/dist/cli/busy.js +10 -12
  6. package/packages/server/dist/cli/doctor.d.ts +60 -0
  7. package/packages/server/dist/cli/doctor.js +580 -0
  8. package/packages/server/dist/cli/exit-codes.d.ts +21 -45
  9. package/packages/server/dist/cli/exit-codes.js +42 -45
  10. package/packages/server/dist/cli/output.js +5 -6
  11. package/packages/server/dist/cli/paddock.js +37 -12
  12. package/packages/server/dist/cli/service/index.d.ts +30 -2
  13. package/packages/server/dist/cli/service/index.js +163 -31
  14. package/packages/server/dist/cli/service/launchd.d.ts +31 -84
  15. package/packages/server/dist/cli/service/launchd.js +114 -304
  16. package/packages/server/dist/cli/service/logs.d.ts +9 -28
  17. package/packages/server/dist/cli/service/logs.js +33 -75
  18. package/packages/server/dist/cli/service/manager.d.ts +93 -0
  19. package/packages/server/dist/cli/service/manager.js +21 -0
  20. package/packages/server/dist/cli/service/path-env.d.ts +7 -18
  21. package/packages/server/dist/cli/service/path-env.js +30 -81
  22. package/packages/server/dist/cli/service/ready.d.ts +16 -23
  23. package/packages/server/dist/cli/service/ready.js +42 -0
  24. package/packages/server/dist/cli/service/spec.d.ts +4 -31
  25. package/packages/server/dist/cli/service/spec.js +9 -0
  26. package/packages/server/dist/cli/service/systemd.d.ts +27 -101
  27. package/packages/server/dist/cli/service/systemd.js +118 -261
  28. package/packages/server/dist/cli/update/health.d.ts +48 -0
  29. package/packages/server/dist/cli/update/health.js +64 -0
  30. package/packages/server/dist/cli/update/install-method.d.ts +29 -76
  31. package/packages/server/dist/cli/update/install-method.js +103 -178
  32. package/packages/server/dist/cli/update/lock.d.ts +7 -12
  33. package/packages/server/dist/cli/update/lock.js +28 -62
  34. package/packages/server/dist/cli/update/package-manager.d.ts +39 -74
  35. package/packages/server/dist/cli/update/package-manager.js +95 -160
  36. package/packages/server/dist/cli/update/registry.d.ts +17 -54
  37. package/packages/server/dist/cli/update/registry.js +46 -89
  38. package/packages/server/dist/cli/update/semver.d.ts +18 -41
  39. package/packages/server/dist/cli/update/semver.js +74 -124
  40. package/packages/server/dist/cli/update/update-check.d.ts +15 -26
  41. package/packages/server/dist/cli/update/update-check.js +30 -60
  42. package/packages/server/dist/cli/update/update.d.ts +57 -91
  43. package/packages/server/dist/cli/update/update.js +230 -431
  44. package/packages/server/dist/cli/update-command.js +3 -5
  45. package/packages/server/dist/runtime-file.d.ts +1 -1
  46. package/packages/server/dist/runtime-file.js +9 -13
  47. package/packages/server/package.json +1 -1
  48. package/packages/web/dist/assets/{DeleteProjectDialog-CphdGB3R.js → DeleteProjectDialog-B07MKgv4.js} +1 -1
  49. package/packages/web/dist/assets/DiscoverPage-D5C2fkeQ.js +2 -0
  50. package/packages/web/dist/assets/{DiscoverView-B_kcF4sm.js → DiscoverView-DpAAyBEf.js} +1 -1
  51. package/packages/web/dist/assets/{InstanceConfigPage-CvUK5DKW.js → InstanceConfigPage-YX0I_jVA.js} +1 -1
  52. package/packages/web/dist/assets/{MarkdownRenderer-DvZ-j_Ao.js → MarkdownRenderer-C-CUuFhT.js} +1 -1
  53. package/packages/web/dist/assets/{ProjectRedirect-DayHvUH2.js → ProjectRedirect-qyXbTK56.js} +1 -1
  54. package/packages/web/dist/assets/{ProjectView-CCw83gUi.js → ProjectView-CguNOv8k.js} +3 -3
  55. package/packages/web/dist/assets/{ProjectsGrid-B3Klg2H4.js → ProjectsGrid-CH2Lz2Hf.js} +1 -1
  56. package/packages/web/dist/assets/{RootHome-BxcDvpgI.js → RootHome-CUGUc8za.js} +1 -1
  57. package/packages/web/dist/assets/{Surfaces-7UlhFBSG.js → Surfaces-11or1q_3.js} +1 -1
  58. package/packages/web/dist/assets/{arc-DNkh5o5Q.js → arc-CQHeCzrk.js} +1 -1
  59. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BrgN3oLF.js → architectureDiagram-3BPJPVTR-dGcuFxAa.js} +1 -1
  60. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-D05XOJSk.js → blockDiagram-GPEHLZMM-CGc9h97S.js} +1 -1
  61. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CKaMBhMP.js → c4Diagram-AAUBKEIU-BpX0sqhH.js} +1 -1
  62. package/packages/web/dist/assets/channel-BAeygx-e.js +2 -0
  63. package/packages/web/dist/assets/{chunk-2J33WTMH-8hgadCQc.js → chunk-2J33WTMH-uORGdO9D.js} +1 -1
  64. package/packages/web/dist/assets/{chunk-4BX2VUAB-CBfEC-pu.js → chunk-4BX2VUAB-C8zI2PFy.js} +1 -1
  65. package/packages/web/dist/assets/{chunk-55IACEB6-DV1eMgHk.js → chunk-55IACEB6-D22Iv6j6.js} +1 -1
  66. package/packages/web/dist/assets/{chunk-727SXJPM-Cfgl5fxY.js → chunk-727SXJPM-DdbHXAtS.js} +1 -1
  67. package/packages/web/dist/assets/{chunk-AQP2D5EJ-BXhqD295.js → chunk-AQP2D5EJ-BeKbob3a.js} +1 -1
  68. package/packages/web/dist/assets/{chunk-FMBD7UC4-TFGOPtek.js → chunk-FMBD7UC4-DtLvFU_y.js} +1 -1
  69. package/packages/web/dist/assets/{chunk-ND2GUHAM-CHFM665R.js → chunk-ND2GUHAM-BWJFo6B1.js} +1 -1
  70. package/packages/web/dist/assets/{chunk-QZHKN3VN-CqEf6MMT.js → chunk-QZHKN3VN-DeFkNg3S.js} +1 -1
  71. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-Cq2-sBMa.js +2 -0
  72. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-Cq2-sBMa.js +2 -0
  73. package/packages/web/dist/assets/{core-DmEaKxBb.js → core-CTNNIHx5.js} +1 -1
  74. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BnGI6jkr.js → cose-bilkent-S5V4N54A-BmtlyA-v.js} +1 -1
  75. package/packages/web/dist/assets/{dagre-BM42HDAG-CKoPx61E.js → dagre-BM42HDAG-oS3rC9CI.js} +1 -1
  76. package/packages/web/dist/assets/{diagram-2AECGRRQ-mMmtlBl1.js → diagram-2AECGRRQ-BzIBqz6A.js} +1 -1
  77. package/packages/web/dist/assets/{diagram-5GNKFQAL-Cdz9Mtnt.js → diagram-5GNKFQAL-BgIT2g8R.js} +1 -1
  78. package/packages/web/dist/assets/{diagram-KO2AKTUF-DQxM3wYj.js → diagram-KO2AKTUF-DACT41Re.js} +1 -1
  79. package/packages/web/dist/assets/{diagram-LMA3HP47-BWrinPsq.js → diagram-LMA3HP47-BstA3UkI.js} +1 -1
  80. package/packages/web/dist/assets/{diagram-OG6HWLK6-U6MYUve2.js → diagram-OG6HWLK6-Dp3Cn5n_.js} +1 -1
  81. package/packages/web/dist/assets/{documentTitle-DGjRXuTP.js → documentTitle-DRSQmA8q.js} +1 -1
  82. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-xqMCsXxY.js → erDiagram-TEJ5UH35-wf8PyGx_.js} +1 -1
  83. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-PMfy_czF.js → flowDiagram-I6XJVG4X-CJ4xx6EQ.js} +1 -1
  84. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-whhYYE4N.js → ganttDiagram-6RSMTGT7-z9obPrPM.js} +1 -1
  85. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-C51JW4tf.js → gitGraphDiagram-PVQCEYII-Cf4Cu7Tk.js} +1 -1
  86. package/packages/web/dist/assets/{index-CHOtvlnO.js → index-Bsv-raz2.js} +2 -2
  87. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-SH8cr905.js → infoDiagram-5YYISTIA-BIfuiYxX.js} +1 -1
  88. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-BjqaoU3U.js → ishikawaDiagram-YF4QCWOH-1pACxMTX.js} +1 -1
  89. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CBmdEVrd.js → journeyDiagram-JHISSGLW-XGbzfw-v.js} +1 -1
  90. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-U8njjMLZ.js → kanban-definition-UN3LZRKU-Bvl-nMak.js} +1 -1
  91. package/packages/web/dist/assets/{linear-DF66GPs1.js → linear-uNToO4bv.js} +1 -1
  92. package/packages/web/dist/assets/{mermaid.core-CouJz7Ph.js → mermaid.core-CrChvyj6.js} +4 -4
  93. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BYIf0oFt.js → mindmap-definition-RKZ34NQL-C6DaQ4Fm.js} +1 -1
  94. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Cm8rgg09.js → pieDiagram-4H26LBE5-CEh8lKyF.js} +1 -1
  95. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-C3Il5kOH.js → quadrantDiagram-W4KKPZXB-z4zvICSM.js} +1 -1
  96. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D3PE2nTe.js → requirementDiagram-4Y6WPE33-BBltkjsj.js} +1 -1
  97. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DdmvBM18.js → sankeyDiagram-5OEKKPKP-D0s95wQ4.js} +1 -1
  98. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-B8M_GAIq.js → sequenceDiagram-3UESZ5HK-kQa65Nbt.js} +1 -1
  99. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DZkXnK1_.js → stateDiagram-AJRCARHV-BqVIoyED.js} +1 -1
  100. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-B6OXxEra.js +2 -0
  101. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-IHXq7rqi.js → timeline-definition-PNZ67QCA-Bo5L62WI.js} +1 -1
  102. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-Dga7tLtT.js → vennDiagram-CIIHVFJN-CN9rH4lT.js} +1 -1
  103. package/packages/web/dist/assets/{wardley-L42UT6IY-CmcwnEJ-.js → wardley-L42UT6IY-BKhnD_W_.js} +1 -1
  104. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-zHuSLrD4.js → wardleyDiagram-YWT4CUSO-XHTbZzXj.js} +1 -1
  105. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-D93MU_ic.js → xychartDiagram-2RQKCTM6--t9XJLbR.js} +1 -1
  106. package/packages/web/dist/index.html +1 -1
  107. package/packages/web/dist/sw.js +1 -1
  108. package/packages/server/dist/cli/service/backend.d.ts +0 -115
  109. package/packages/server/dist/cli/service/backend.js +0 -19
  110. package/packages/web/dist/assets/DiscoverPage-C4OPLNO8.js +0 -2
  111. package/packages/web/dist/assets/channel-JrEedxB8.js +0 -2
  112. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CHJiQp-W.js +0 -2
  113. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CHJiQp-W.js +0 -2
  114. 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 `-`, empty for a release. */
5
+ /** The dot-separated identifiers after `-`; empty for a release. */
24
6
  prerelease: (string | number)[];
25
7
  }
26
- /** `1.2.3-rc.1` → a {@link Version}, or undefined if it is not a version. */
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
- /** Is this a prerelease? SPEC §5: prereleases count only when `--to` names them. */
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 the semver rules — including the prerelease ones, which are
32
- * the only interesting part: `1.0.0-rc.1` is LOWER than `1.0.0`, numeric
33
- * identifiers compare numerically and sort below alphanumeric ones, and a
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
- /** Is `a` strictly newer than `b`? */
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 unparseable. Undefined if none parse. */
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
- * Does `version` satisfy `range`? Undefined means "this file cannot tell".
25
+ * Whether `version` satisfies `range`; undefined when this file can't tell.
45
26
  *
46
- * Supports the forms an `engines.node` actually uses: `>=22`, `>=22.0.0`,
47
- * `^22.1.0`, `~22.1`, `22`, `22.x`, `>=20 <23`, and `||` between any of those.
48
- * `*` and an empty range accept everything.
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
- /** True pass, false genuine fail, undefined when the range could not be read. */
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 version run on this Node? (SPEC §5 step 4.)
59
- *
60
- * The three-way answer is the point. A target with no `engines.node` at all, or
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
- * 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
- */
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
- /** `1.2.3-rc.1` → a {@link Version}, or undefined if it is not a version. */
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
- const prerelease = m[4] === undefined
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
- /** Is this a prerelease? SPEC §5: prereleases count only when `--to` names them. */
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 the semver rules — including the prerelease ones, which are
36
- * the only interesting part: `1.0.0-rc.1` is LOWER than `1.0.0`, numeric
37
- * identifiers compare numerically and sort below alphanumeric ones, and a
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
- for (const k of ["major", "minor", "patch"]) {
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
- const xNum = typeof x === "number";
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 (xNum)
56
+ if (typeof x === 'number')
75
57
  return -1;
76
- if (yNum)
58
+ if (typeof y === 'number')
77
59
  return 1;
78
- return String(x) < String(y) ? -1 : 1;
60
+ return x < y ? -1 : 1;
79
61
  }
80
62
  return 0;
81
63
  }
82
- /** Is `a` strictly newer than `b`? */
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 unparseable. Undefined if none parse. */
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
- * Expand one range atom into the comparators it means.
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 prerelease = m[5] === undefined ? [] : m[5].split(".").map((id) => (/^\d+$/.test(id) ? Number(id) : id));
116
- const at = (ma, mi, pa) => ({
117
- major: ma,
118
- minor: mi,
119
- patch: pa,
120
- prerelease: [],
121
- });
122
- const lower = { major, minor, patch, prerelease };
123
- if (op === "^") {
124
- // `^0.x` tightens: ^0.2.3 is >=0.2.3 <0.3.0, ^0.0.3 is >=0.0.3 <0.0.4.
125
- const upper = major > 0
126
- ? at(major + 1, 0, 0)
127
- : minor > 0 || !minorWild
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
- * Does `version` satisfy `range`? Undefined means "this file cannot tell".
139
+ * Whether `version` satisfies `range`; undefined when this file can't tell.
182
140
  *
183
- * Supports the forms an `engines.node` actually uses: `>=22`, `>=22.0.0`,
184
- * `^22.1.0`, `~22.1`, `22`, `22.x`, `>=20 <23`, and `||` between any of those.
185
- * `*` and an empty range accept everything.
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 === "" || trimmed === "*" || trimmed === "x")
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 alternatives) {
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 atoms) {
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 version run on this Node? (SPEC §5 step 4.)
220
- *
221
- * The three-way answer is the point. A target with no `engines.node` at all, or
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 "./install-method.js";
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. Matches `schemas/update-check.schema.json`. */
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
- /** What `service status --json` carries (`schemas/service-status.schema.json`). */
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
- * Write the cache atomically.
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
- * The summary `service status` shows, or undefined when there is nothing worth
51
- * repeating: no cache, a cache from a different install, or one too old.
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
- /** `paddock update`, or the command this install needs instead (SPEC §3, §6). */
54
+ /** `<tool> update`, or the command this install needs instead. */
62
55
  command: string;
63
- /** The parenthetical after the command: `the global folder isn't writable`. */
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 update notice, verbatim in shape:
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
- * `<home>/update-check.json` — what the last registry question found
3
- * (CLI contract SPEC §3).
4
- *
5
- * The contract's rule is that **nothing contacts the registry by itself**: only
6
- * `update`, `update --check` and `doctor` ask, and only when a person runs
7
- * them. But `service status` should still be able to mention a newer version,
8
- * because that is where someone looks. The cache is how both hold: the three
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
- * Write the cache atomically.
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
- /* a cache that could not be written is not a failed update */
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), "utf8"));
54
- if (typeof raw.name === "string" &&
55
- typeof raw.current === "string" &&
56
- typeof raw.target === "string" &&
57
- typeof raw.updateAvailable === "boolean" &&
58
- typeof raw.checkedAt === "string") {
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
- /* missing, half-written, or from something else */
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
- * The summary `service status` shows, or undefined when there is nothing worth
79
- * repeating: no cache, a cache from a different install, or one too old.
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
- const at = Date.parse(check.checkedAt);
86
- if (!Number.isFinite(at))
65
+ if (o.currentVersion !== undefined && check.current !== o.currentVersion)
87
66
  return undefined;
88
- const age = (o.now ?? Date.now()) - at;
89
- if (age > (o.maxAgeMs ?? UPDATE_CHECK_MAX_AGE_MS))
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 update notice, verbatim in shape:
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("\n");
85
+ return lines.join('\n');
116
86
  }