Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

/** Read and validate the package.json version string. */
export function readVersionFromPackageJson(packageJsonPath: string): string {
const pkg = JSON.parse(
readFileSync(packageJsonPath, "utf8"),
) as { version?: unknown };

if (typeof pkg.version !== "string" || pkg.version.trim().length === 0) {
throw new Error("package.json is missing a valid version field.");
}

return pkg.version;
}

/**
* CLI version, read from package.json at runtime so it can never drift from the
* published version (see #48 — this used to be a hard-coded literal that fell
Expand All @@ -15,8 +28,5 @@ import { fileURLToPath } from "node:url";
* plain runtime read instead of a copied asset.
*/
const here = dirname(fileURLToPath(import.meta.url));
const pkg = JSON.parse(
readFileSync(join(here, "..", "package.json"), "utf8"),
) as { version: string };

export const VERSION = pkg.version;
export const VERSION = readVersionFromPackageJson(join(here, "..", "package.json"));
55 changes: 55 additions & 0 deletions test/version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { afterEach, describe, expect, it } from "vitest";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { readVersionFromPackageJson, VERSION } from "../src/version.js";

const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as {
version: string;
};

const tmpDirs: string[] = [];

function writePackageJson(contents: unknown): string {
const tmpDir = mkdtempSync(join(tmpdir(), "mex-version-"));
tmpDirs.push(tmpDir);
const packageJsonPath = join(tmpDir, "package.json");
writeFileSync(packageJsonPath, JSON.stringify(contents));
return packageJsonPath;
}

afterEach(() => {
while (tmpDirs.length > 0) {
const tmpDir = tmpDirs.pop();
if (!tmpDir) continue;
rmSync(tmpDir, { recursive: true, force: true });
}
});

describe("version metadata", () => {
it("exports the root package.json version", () => {
expect(VERSION).toBe(packageJson.version);
});

it("reads a valid package version", () => {
const packageJsonPath = writePackageJson({ version: "1.2.3" });

expect(readVersionFromPackageJson(packageJsonPath)).toBe("1.2.3");
});

it("rejects missing, empty, or non-string versions", () => {
for (const contents of [
{},
{ version: "" },
{ version: " " },
{ version: 1 },
{ version: null },
]) {
const packageJsonPath = writePackageJson(contents);

expect(() => readVersionFromPackageJson(packageJsonPath)).toThrow(
"package.json is missing a valid version field.",
);
}
});
});
Loading