diffninja 0.3.0 → 0.3.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/README.md +6 -3
- package/dist/review/mcp.js +2 -1
- package/dist/review/setup.d.ts +24 -6
- package/dist/review/setup.js +98 -30
- package/dist/review/version.d.ts +14 -0
- package/dist/review/version.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,9 +48,12 @@ npx -y diffninja@latest setup
|
|
|
48
48
|
It installs diffninja and adds it to every agent CLI it finds on your machine.
|
|
49
49
|
Then restart your agent CLI.
|
|
50
50
|
|
|
51
|
-
To update later, run the same command again
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
To update later, run the same command again: setup brings an older global
|
|
52
|
+
install up to its own version, then restart your agent CLI. (Setup from
|
|
53
|
+
0.3.0 or earlier does not update an existing install; if yours reports an
|
|
54
|
+
older version, run `npm install -g diffninja@latest` once.) To remove
|
|
55
|
+
diffninja from your agent CLIs, run `npx -y diffninja@latest setup --uninstall`.
|
|
56
|
+
Other options: `npx -y diffninja@latest setup --help`.
|
|
54
57
|
|
|
55
58
|
## How to use it
|
|
56
59
|
|
package/dist/review/mcp.js
CHANGED
|
@@ -10,6 +10,7 @@ import { renderBusinessPage, renderCallFlowPage, renderReview } from "./html.js"
|
|
|
10
10
|
import { MAX_SUGGESTED_COMMENTS, ReportPages } from "./report-pages.js";
|
|
11
11
|
import { reviewDiff } from "./service.js";
|
|
12
12
|
import { MAX_BRANCH_CHARS, MAX_DETAIL_CHARS, MAX_EXPLAINED_FUNCTIONS, MAX_PROCESSES, MAX_PROCESS_STEPS, MAX_PURPOSE_CHARS, MAX_RULES, MAX_RULE_CHARS, MAX_STEP_CHARS, MAX_STEP_EXITS, MAX_TITLE_CHARS, MIN_PROCESS_STEPS, } from "./explanation.js";
|
|
13
|
+
import { packageVersion } from "./version.js";
|
|
13
14
|
const PR_LINK_ERROR = "A pull request review needs exactly one full github.com pull request URL, for example https://github.com/OWNER/REPO/pull/123. Ask the user for their link; do not guess, search, or invent one.";
|
|
14
15
|
const STATIC_MODE_ERROR = "mode static reviews a diff or git range and accepts no pr or input. Use mode connected to review a pull request link.";
|
|
15
16
|
/**
|
|
@@ -220,7 +221,7 @@ class ReviewServer extends McpServer {
|
|
|
220
221
|
sessions;
|
|
221
222
|
reports;
|
|
222
223
|
constructor(sessions, reports) {
|
|
223
|
-
super({ name: "diffninja", version:
|
|
224
|
+
super({ name: "diffninja", version: packageVersion() });
|
|
224
225
|
this.sessions = sessions;
|
|
225
226
|
this.reports = reports;
|
|
226
227
|
this.server.onclose = () => {
|
package/dist/review/setup.d.ts
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* agent CLI (Claude Code, Codex, OMP, pi) with one command.
|
|
4
4
|
*
|
|
5
5
|
* The setup installs the package globally first so the registration points
|
|
6
|
-
* at a permanent binary instead of the npx cache
|
|
7
|
-
*
|
|
6
|
+
* at a permanent binary instead of the npx cache, and brings a global install
|
|
7
|
+
* older than itself up to its own version, so running the newest setup is how
|
|
8
|
+
* a user updates. When the global install is unavailable it falls back to an
|
|
9
|
+
* npx-based entry pinned to its own version and says so. The server
|
|
8
10
|
* needs no key or environment: reviews are local, and connected reviews reuse
|
|
9
11
|
* the `gh` session.
|
|
10
12
|
*/
|
|
11
13
|
import { type Stats } from "node:fs";
|
|
12
|
-
export declare const setupHelp = "diffninja setup. Register the diffninja MCP server on every detected agent CLI.\n\n npx -y diffninja setup [--cli claude,codex,omp,pi] [--dry-run]\n diffninja setup --uninstall [--cli codex]\n\nDetects Claude Code, Codex, OMP, and pi from their config files or binaries\nand registers the diffninja MCP server in each user config, pointing at the\nglobally installed package. Installs the package globally first\n(`npm install -g diffninja
|
|
14
|
+
export declare const setupHelp = "diffninja setup. Register the diffninja MCP server on every detected agent CLI.\n\n npx -y diffninja@latest setup [--cli claude,codex,omp,pi] [--dry-run]\n diffninja setup --uninstall [--cli codex]\n\nDetects Claude Code, Codex, OMP, and pi from their config files or binaries\nand registers the diffninja MCP server in each user config, pointing at the\nglobally installed package. Installs the package globally first\n(`npm install -g diffninja@<this version>`) so the registration keeps\nworking, and updates a global install older than this setup, so running\n`npx -y diffninja@latest setup` again is how you update. When that install\nfails it registers an npx-based entry pinned to this version and says so.\n\nOptions:\n --cli NAMES Only these CLIs, comma-separated: claude,codex,omp,pi.\n --uninstall Remove the diffninja server from every detected CLI.\n --dry-run Show what would change, without installing or writing.\n --no-install Skip installing or updating the global package; without one,\n register npx-based entries.\n --help Show this help.\n\nThe server needs no API key: static reviews run locally, and pull request\nreviews reuse your authenticated gh session.\n";
|
|
13
15
|
export declare const CLI_NAMES: readonly ["claude", "codex", "omp", "pi"];
|
|
14
16
|
export type CliName = (typeof CLI_NAMES)[number];
|
|
15
17
|
export interface McpEntry {
|
|
@@ -29,7 +31,8 @@ export interface SetupOptions {
|
|
|
29
31
|
}
|
|
30
32
|
export interface Npm {
|
|
31
33
|
rootG(): Promise<string>;
|
|
32
|
-
|
|
34
|
+
/** `npm install -g <spec>`, e.g. `diffninja@0.3.1`; true on success. */
|
|
35
|
+
installG(spec: string): Promise<boolean>;
|
|
33
36
|
}
|
|
34
37
|
export interface CliReport {
|
|
35
38
|
cli: CliName;
|
|
@@ -44,9 +47,16 @@ export interface SetupReport {
|
|
|
44
47
|
}
|
|
45
48
|
export interface SetupDeps {
|
|
46
49
|
npm?: Npm;
|
|
50
|
+
/** Version this setup installs and pins; defaults to the running package's. */
|
|
51
|
+
version?: string;
|
|
47
52
|
}
|
|
48
|
-
/**
|
|
49
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Entry that runs the published package on demand, without an install. It
|
|
55
|
+
* names this setup's version: an unversioned package lets npx keep running
|
|
56
|
+
* whichever copy it cached first, while a pinned one changes with each newer
|
|
57
|
+
* setup, which rewrites the entry.
|
|
58
|
+
*/
|
|
59
|
+
export declare function npxEntry(platform?: NodeJS.Platform, version?: string): McpEntry;
|
|
50
60
|
/**
|
|
51
61
|
* Entry for a command that Windows ships only as a `.cmd` shim, which a client
|
|
52
62
|
* that spawns without a shell cannot launch. Running npm's JS entry point with
|
|
@@ -60,6 +70,14 @@ export declare function windowsCliEntry(name: string, args: readonly string[], c
|
|
|
60
70
|
/** Entry that runs the globally installed package. `globalRoot` is `npm root -g`. */
|
|
61
71
|
export declare function globalEntry(globalRoot: string): McpEntry;
|
|
62
72
|
export declare function globalEntryExists(globalRoot: string): boolean;
|
|
73
|
+
/** Version of the globally installed package, or undefined when its manifest is missing or unreadable. */
|
|
74
|
+
export declare function globalVersion(globalRoot: string): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Whether a global install at `installed` should be brought up to `version`.
|
|
77
|
+
* A manifest that cannot be read or compared counts as older, so a broken
|
|
78
|
+
* install is repaired; a newer one is never downgraded.
|
|
79
|
+
*/
|
|
80
|
+
export declare function globalIsOlder(installed: string | undefined, version: string): boolean;
|
|
63
81
|
/**
|
|
64
82
|
* npm runner. Shell-free by design: on Windows npm is a `.cmd` shim, so runs
|
|
65
83
|
* go through `npmSpawnSpec`, which resolves npm's JS entry point instead.
|
package/dist/review/setup.js
CHANGED
|
@@ -3,37 +3,43 @@
|
|
|
3
3
|
* agent CLI (Claude Code, Codex, OMP, pi) with one command.
|
|
4
4
|
*
|
|
5
5
|
* The setup installs the package globally first so the registration points
|
|
6
|
-
* at a permanent binary instead of the npx cache
|
|
7
|
-
*
|
|
6
|
+
* at a permanent binary instead of the npx cache, and brings a global install
|
|
7
|
+
* older than itself up to its own version, so running the newest setup is how
|
|
8
|
+
* a user updates. When the global install is unavailable it falls back to an
|
|
9
|
+
* npx-based entry pinned to its own version and says so. The server
|
|
8
10
|
* needs no key or environment: reviews are local, and connected reviews reuse
|
|
9
11
|
* the `gh` session.
|
|
10
12
|
*/
|
|
11
13
|
import { spawn } from "node:child_process";
|
|
12
14
|
import { randomBytes } from "node:crypto";
|
|
13
15
|
import { once } from "node:events";
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
17
|
import { lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat } from "node:fs/promises";
|
|
16
18
|
import { homedir } from "node:os";
|
|
17
19
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
18
20
|
import { z } from "zod";
|
|
19
21
|
import { npmCliPath, npmSpawnSpec } from "../languages/grammars.js";
|
|
20
22
|
import { removeTomlTable, upsertTomlTable } from "./toml.js";
|
|
23
|
+
import { compareVersions, packageVersion } from "./version.js";
|
|
21
24
|
export const setupHelp = `diffninja setup. Register the diffninja MCP server on every detected agent CLI.
|
|
22
25
|
|
|
23
|
-
npx -y diffninja setup [--cli claude,codex,omp,pi] [--dry-run]
|
|
26
|
+
npx -y diffninja@latest setup [--cli claude,codex,omp,pi] [--dry-run]
|
|
24
27
|
diffninja setup --uninstall [--cli codex]
|
|
25
28
|
|
|
26
29
|
Detects Claude Code, Codex, OMP, and pi from their config files or binaries
|
|
27
30
|
and registers the diffninja MCP server in each user config, pointing at the
|
|
28
31
|
globally installed package. Installs the package globally first
|
|
29
|
-
(\`npm install -g diffninja
|
|
30
|
-
|
|
32
|
+
(\`npm install -g diffninja@<this version>\`) so the registration keeps
|
|
33
|
+
working, and updates a global install older than this setup, so running
|
|
34
|
+
\`npx -y diffninja@latest setup\` again is how you update. When that install
|
|
35
|
+
fails it registers an npx-based entry pinned to this version and says so.
|
|
31
36
|
|
|
32
37
|
Options:
|
|
33
38
|
--cli NAMES Only these CLIs, comma-separated: claude,codex,omp,pi.
|
|
34
39
|
--uninstall Remove the diffninja server from every detected CLI.
|
|
35
40
|
--dry-run Show what would change, without installing or writing.
|
|
36
|
-
--no-install Skip the global
|
|
41
|
+
--no-install Skip installing or updating the global package; without one,
|
|
42
|
+
register npx-based entries.
|
|
37
43
|
--help Show this help.
|
|
38
44
|
|
|
39
45
|
The server needs no API key: static reviews run locally, and pull request
|
|
@@ -42,9 +48,14 @@ reviews reuse your authenticated gh session.
|
|
|
42
48
|
export const CLI_NAMES = ["claude", "codex", "omp", "pi"];
|
|
43
49
|
const jsonValueSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)]));
|
|
44
50
|
const jsonObjectSchema = z.object({}).catchall(jsonValueSchema);
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Entry that runs the published package on demand, without an install. It
|
|
53
|
+
* names this setup's version: an unversioned package lets npx keep running
|
|
54
|
+
* whichever copy it cached first, while a pinned one changes with each newer
|
|
55
|
+
* setup, which rewrites the entry.
|
|
56
|
+
*/
|
|
57
|
+
export function npxEntry(platform = process.platform, version = packageVersion()) {
|
|
58
|
+
const args = ["-y", "-p", `diffninja@${version}`, "diffninja-mcp"];
|
|
48
59
|
return platform === "win32" ? windowsCliEntry("npx", args) : { command: "npx", args };
|
|
49
60
|
}
|
|
50
61
|
/**
|
|
@@ -70,6 +81,27 @@ export function globalEntry(globalRoot) {
|
|
|
70
81
|
export function globalEntryExists(globalRoot) {
|
|
71
82
|
return existsSync(join(globalRoot, "diffninja", "dist", "review", "mcp-cli.js"));
|
|
72
83
|
}
|
|
84
|
+
const installedManifestSchema = z.object({ version: z.string() });
|
|
85
|
+
/** Version of the globally installed package, or undefined when its manifest is missing or unreadable. */
|
|
86
|
+
export function globalVersion(globalRoot) {
|
|
87
|
+
try {
|
|
88
|
+
const manifest = installedManifestSchema.safeParse(JSON.parse(readFileSync(join(globalRoot, "diffninja", "package.json"), "utf8")));
|
|
89
|
+
return manifest.success ? manifest.data.version : undefined;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a global install at `installed` should be brought up to `version`.
|
|
97
|
+
* A manifest that cannot be read or compared counts as older, so a broken
|
|
98
|
+
* install is repaired; a newer one is never downgraded.
|
|
99
|
+
*/
|
|
100
|
+
export function globalIsOlder(installed, version) {
|
|
101
|
+
if (installed === undefined)
|
|
102
|
+
return true;
|
|
103
|
+
return (compareVersions(installed, version) ?? -1) < 0;
|
|
104
|
+
}
|
|
73
105
|
/**
|
|
74
106
|
* npm runner. Shell-free by design: on Windows npm is a `.cmd` shim, so runs
|
|
75
107
|
* go through `npmSpawnSpec`, which resolves npm's JS entry point instead.
|
|
@@ -90,8 +122,8 @@ export function createNpm(overrides = {}) {
|
|
|
90
122
|
});
|
|
91
123
|
return (await exitCode(child)) === 0 ? out.trim() : "";
|
|
92
124
|
},
|
|
93
|
-
async installG() {
|
|
94
|
-
return (await exitCode(run(["install", "-g", `--allow-scripts=${INSTALL_SCRIPT_PACKAGES.join(",")}`,
|
|
125
|
+
async installG(spec) {
|
|
126
|
+
return (await exitCode(run(["install", "-g", `--allow-scripts=${INSTALL_SCRIPT_PACKAGES.join(",")}`, spec], true))) === 0;
|
|
95
127
|
},
|
|
96
128
|
};
|
|
97
129
|
}
|
|
@@ -123,35 +155,70 @@ async function globalRoot(npm) {
|
|
|
123
155
|
return undefined;
|
|
124
156
|
}
|
|
125
157
|
}
|
|
158
|
+
/** Run one global install; a throw counts as a failed install. */
|
|
159
|
+
async function installGlobal(npm, spec) {
|
|
160
|
+
try {
|
|
161
|
+
return await npm.installG(spec);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
126
167
|
/**
|
|
127
|
-
* Resolve the registration entry.
|
|
128
|
-
*
|
|
168
|
+
* Resolve the registration entry. A global install older than `version` is
|
|
169
|
+
* updated to it first; one that cannot be updated keeps its entry, with a
|
|
170
|
+
* warning naming the command that updates it. `--dry-run` never installs: it
|
|
171
|
+
* reports the install it would have run and the entry that install would have
|
|
172
|
+
* produced.
|
|
129
173
|
*/
|
|
130
|
-
async function resolveEntry(noInstall, npm, quiet, dryRun) {
|
|
174
|
+
async function resolveEntry(noInstall, npm, quiet, dryRun, version) {
|
|
175
|
+
const spec = `diffninja@${version}`;
|
|
131
176
|
const root = await globalRoot(npm);
|
|
132
|
-
if (root !== undefined && globalEntryExists(root))
|
|
177
|
+
if (root !== undefined && globalEntryExists(root)) {
|
|
178
|
+
const installed = globalVersion(root);
|
|
179
|
+
if (!globalIsOlder(installed, version))
|
|
180
|
+
return { entry: globalEntry(root), viaNpx: false };
|
|
181
|
+
const from = installed ?? "an unknown version";
|
|
182
|
+
const update = `npm install -g ${spec}`;
|
|
183
|
+
if (noInstall) {
|
|
184
|
+
if (!quiet)
|
|
185
|
+
console.error(`diffninja: the global install is ${from}, older than this setup (${version}); update it with ${update}.`);
|
|
186
|
+
}
|
|
187
|
+
else if (dryRun) {
|
|
188
|
+
if (!quiet)
|
|
189
|
+
console.log(`diffninja: dry run: would update the global install from ${from} to ${version} (${update}).`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
if (!quiet)
|
|
193
|
+
console.log(`diffninja: updating the global install from ${from} to ${version} (${update})...`);
|
|
194
|
+
const updated = await installGlobal(npm, spec);
|
|
195
|
+
const updatedRoot = updated ? await globalRoot(npm) : undefined;
|
|
196
|
+
if (updatedRoot !== undefined && globalEntryExists(updatedRoot))
|
|
197
|
+
return { entry: globalEntry(updatedRoot), viaNpx: false };
|
|
198
|
+
if (!globalEntryExists(root)) {
|
|
199
|
+
if (!quiet)
|
|
200
|
+
console.error("diffninja: global update failed and left no install; registering npx-based entries instead.");
|
|
201
|
+
return { entry: npxEntry(process.platform, version), viaNpx: true };
|
|
202
|
+
}
|
|
203
|
+
if (!quiet)
|
|
204
|
+
console.error(`diffninja: update failed; the global install is still ${globalVersion(root) ?? from}. Update it with ${update}.`);
|
|
205
|
+
}
|
|
133
206
|
return { entry: globalEntry(root), viaNpx: false };
|
|
207
|
+
}
|
|
134
208
|
if (dryRun) {
|
|
135
209
|
if (!noInstall && root !== undefined) {
|
|
136
210
|
if (!quiet)
|
|
137
|
-
console.log(
|
|
211
|
+
console.log(`diffninja: dry run: would install the package globally (npm install -g ${spec}).`);
|
|
138
212
|
return { entry: globalEntry(root), viaNpx: false };
|
|
139
213
|
}
|
|
140
214
|
if (!quiet)
|
|
141
215
|
console.error("diffninja: dry run: global install unavailable; would register npx-based entries instead.");
|
|
142
|
-
return { entry: npxEntry(), viaNpx: true };
|
|
216
|
+
return { entry: npxEntry(process.platform, version), viaNpx: true };
|
|
143
217
|
}
|
|
144
218
|
if (!noInstall) {
|
|
145
219
|
if (!quiet)
|
|
146
|
-
console.log(
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
installed = await npm.installG();
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
installed = false;
|
|
153
|
-
}
|
|
154
|
-
if (installed) {
|
|
220
|
+
console.log(`diffninja: installing the package globally (npm install -g ${spec})...`);
|
|
221
|
+
if (await installGlobal(npm, spec)) {
|
|
155
222
|
const installedRoot = await globalRoot(npm);
|
|
156
223
|
if (installedRoot !== undefined && globalEntryExists(installedRoot)) {
|
|
157
224
|
return { entry: globalEntry(installedRoot), viaNpx: false };
|
|
@@ -160,7 +227,7 @@ async function resolveEntry(noInstall, npm, quiet, dryRun) {
|
|
|
160
227
|
}
|
|
161
228
|
if (!quiet)
|
|
162
229
|
console.error("diffninja: global install unavailable; registering npx-based entries instead.");
|
|
163
|
-
return { entry: npxEntry(), viaNpx: true };
|
|
230
|
+
return { entry: npxEntry(process.platform, version), viaNpx: true };
|
|
164
231
|
}
|
|
165
232
|
function findOnPath(name, pathDirs) {
|
|
166
233
|
const candidates = process.platform === "win32" ? [name, `${name}.cmd`, `${name}.exe`] : [name];
|
|
@@ -486,10 +553,11 @@ export async function runSetup(options = {}, deps = {}) {
|
|
|
486
553
|
const quiet = options.quiet === true;
|
|
487
554
|
const uninstall = options.uninstall === true;
|
|
488
555
|
const dryRun = options.dryRun === true;
|
|
556
|
+
const version = deps.version ?? packageVersion();
|
|
489
557
|
let entry;
|
|
490
558
|
let viaNpx = false;
|
|
491
559
|
if (!uninstall) {
|
|
492
|
-
const resolved = await resolveEntry(options.noInstall === true, deps.npm ?? realNpm, quiet, dryRun);
|
|
560
|
+
const resolved = await resolveEntry(options.noInstall === true, deps.npm ?? realNpm, quiet, dryRun, version);
|
|
493
561
|
entry = resolved.entry;
|
|
494
562
|
viaNpx = resolved.viaNpx;
|
|
495
563
|
}
|
|
@@ -545,5 +613,5 @@ export async function runSetup(options = {}, deps = {}) {
|
|
|
545
613
|
console.log("note: pi needs the pi-mcp-extension for MCP support (pi install npm:pi-mcp-extension)");
|
|
546
614
|
}
|
|
547
615
|
}
|
|
548
|
-
return { entry: entry ?? npxEntry(), viaNpx, clis: reports };
|
|
616
|
+
return { entry: entry ?? npxEntry(process.platform, version), viaNpx, clis: reports };
|
|
549
617
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version of the running diffninja package, read from its own
|
|
3
|
+
* `package.json`. Both layouts put it two levels up: `src/review/` in a
|
|
4
|
+
* checkout and `dist/review/` in the published package, which always ships
|
|
5
|
+
* `package.json`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function packageVersion(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Compare two `major.minor.patch` versions: negative when `left` is older,
|
|
10
|
+
* zero when equal, positive when newer. A prerelease sorts before its release;
|
|
11
|
+
* two prereleases of one release compare by their tags as text. Undefined when
|
|
12
|
+
* either is not a version.
|
|
13
|
+
*/
|
|
14
|
+
export declare function compareVersions(left: string, right: string): number | undefined;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version of the running diffninja package, read from its own
|
|
3
|
+
* `package.json`. Both layouts put it two levels up: `src/review/` in a
|
|
4
|
+
* checkout and `dist/review/` in the published package, which always ships
|
|
5
|
+
* `package.json`.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
const manifestSchema = z.object({ version: z.string() });
|
|
10
|
+
let cached;
|
|
11
|
+
export function packageVersion() {
|
|
12
|
+
cached ??= manifestSchema.parse(JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"))).version;
|
|
13
|
+
return cached;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compare two `major.minor.patch` versions: negative when `left` is older,
|
|
17
|
+
* zero when equal, positive when newer. A prerelease sorts before its release;
|
|
18
|
+
* two prereleases of one release compare by their tags as text. Undefined when
|
|
19
|
+
* either is not a version.
|
|
20
|
+
*/
|
|
21
|
+
export function compareVersions(left, right) {
|
|
22
|
+
const a = parseVersion(left);
|
|
23
|
+
const b = parseVersion(right);
|
|
24
|
+
if (a === undefined || b === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
for (let index = 0; index < 3; index += 1) {
|
|
27
|
+
const difference = a.core[index] - b.core[index];
|
|
28
|
+
if (difference !== 0)
|
|
29
|
+
return difference;
|
|
30
|
+
}
|
|
31
|
+
if (a.prerelease === b.prerelease)
|
|
32
|
+
return 0;
|
|
33
|
+
if (a.prerelease === undefined)
|
|
34
|
+
return 1;
|
|
35
|
+
if (b.prerelease === undefined)
|
|
36
|
+
return -1;
|
|
37
|
+
return a.prerelease < b.prerelease ? -1 : 1;
|
|
38
|
+
}
|
|
39
|
+
function parseVersion(text) {
|
|
40
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(text.trim());
|
|
41
|
+
if (match === null)
|
|
42
|
+
return undefined;
|
|
43
|
+
return { core: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4] };
|
|
44
|
+
}
|