diff --git a/package.json b/package.json index 1ffec9b..9311a9b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "node": "^22 || ^24" }, "bin": { - "github-delivery": "./scripts/github-delivery-cli.mjs" + "github-delivery": "scripts/github-delivery-cli.mjs" }, "files": [ "scripts/github-delivery-cli.mjs", @@ -26,7 +26,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/Wibias/github-delivery.git" + "url": "git+https://github.com/Wibias/github-delivery.git" }, "publishConfig": { "access": "public", diff --git a/scripts/lib/npm-pack-json.mjs b/scripts/lib/npm-pack-json.mjs new file mode 100644 index 0000000..6450d1f --- /dev/null +++ b/scripts/lib/npm-pack-json.mjs @@ -0,0 +1,24 @@ +export function parseNpmPackJson(stdout) { + const text = String(stdout || "").trim(); + if (!text) throw new Error("npm_pack_json_missing"); + + let parsed; + try { + parsed = JSON.parse(text); + } catch (error) { + throw new Error(`npm_pack_json_invalid:${error?.message || String(error)}`); + } + + if (Array.isArray(parsed)) return parsed; + + if (parsed && typeof parsed === "object") { + if (Array.isArray(parsed.files)) return [parsed]; + + const packs = Object.values(parsed).filter( + (value) => value && typeof value === "object" && Array.isArray(value.files), + ); + if (packs.length > 0) return packs; + } + + throw new Error("npm_pack_json_shape_invalid"); +} diff --git a/scripts/validate-npm-package.mjs b/scripts/validate-npm-package.mjs index f4e152c..a38d547 100644 --- a/scripts/validate-npm-package.mjs +++ b/scripts/validate-npm-package.mjs @@ -5,6 +5,8 @@ import { resolve } from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; +import { parseNpmPackJson } from "./lib/npm-pack-json.mjs"; + const ROOT = resolve(fileURLToPath(new URL("..", import.meta.url))); const NPM = process.platform === "win32" ? "npm.cmd" : "npm"; @@ -31,16 +33,6 @@ function fail(message) { process.exit(1); } -function parsePackJson(stdout) { - const text = String(stdout || "").trim(); - const first = text.indexOf("["); - const last = text.lastIndexOf("]"); - if (first === -1 || last === -1 || last < first) { - throw new Error("npm_pack_json_missing"); - } - return JSON.parse(text.slice(first, last + 1)); -} - function sameStrings(actual, expected) { return actual.length === expected.length && actual.every((value, index) => value === expected[index]); @@ -51,12 +43,12 @@ try { assert.equal(pkg.name, "github-delivery"); assert.equal(pkg.private, undefined); assert.deepEqual(pkg.bin, { - "github-delivery": "./scripts/github-delivery-cli.mjs", + "github-delivery": "scripts/github-delivery-cli.mjs", }); assert.equal(pkg.license, "MIT"); assert.deepEqual(pkg.repository, { type: "git", - url: "https://github.com/Wibias/github-delivery.git", + url: "git+https://github.com/Wibias/github-delivery.git", }); assert.equal(pkg.publishConfig?.access, "public"); assert.equal(pkg.publishConfig?.registry, "https://registry.npmjs.org/"); @@ -83,7 +75,7 @@ try { }, }); assert.equal(packResult.status, 0, packResult.stderr || packResult.stdout); - const [pack] = parsePackJson(packResult.stdout); + const [pack] = parseNpmPackJson(packResult.stdout); assert(pack, "npm pack returned no package metadata"); const actual = pack.files.map((entry) => entry.path).sort(); diff --git a/tests/unit/npm-package.test.mjs b/tests/unit/npm-package.test.mjs index b5a96e7..541d734 100644 --- a/tests/unit/npm-package.test.mjs +++ b/tests/unit/npm-package.test.mjs @@ -5,6 +5,8 @@ import { join, resolve } from "node:path"; import { spawnSync } from "node:child_process"; import test from "node:test"; +import { parseNpmPackJson } from "../../scripts/lib/npm-pack-json.mjs"; + const ROOT = resolve(import.meta.dirname, "../.."); const NPM = process.platform === "win32" ? "npm.cmd" : "npm"; @@ -43,36 +45,36 @@ function runNpm(args, cwd = ROOT) { }); } -function parsePackJson(stdout) { - const text = String(stdout || "").trim(); - const first = text.indexOf("["); - const last = text.lastIndexOf("]"); - if (first === -1 || last === -1 || last < first) { - throw new Error(`npm_pack_json_missing:${text.slice(0, 400)}`); - } - return JSON.parse(text.slice(first, last + 1)); -} - function dryRunPack() { const result = runNpm(["pack", "--dry-run", "--json", "--ignore-scripts"]); assert.equal(result.status, 0, result.stderr || result.stdout); - const [pack] = parsePackJson(result.stdout); + const [pack] = parseNpmPackJson(result.stdout); assert(pack, "npm pack returned no package metadata"); return pack; } +test("npm pack parser accepts npm 11 array and npm 12 keyed-object output", () => { + const pack = { + name: "github-delivery", + version: "0.5.0", + files: [{ path: "package.json" }], + }; + assert.deepEqual(parseNpmPackJson(JSON.stringify([pack])), [pack]); + assert.deepEqual(parseNpmPackJson(JSON.stringify({ "github-delivery": pack })), [pack]); +}); + test("package metadata exposes only the supported public npx bootstrap", () => { const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); assert.equal(pkg.name, "github-delivery"); assert.equal(pkg.private, undefined); assert.deepEqual(pkg.bin, { - "github-delivery": "./scripts/github-delivery-cli.mjs", + "github-delivery": "scripts/github-delivery-cli.mjs", }); assert.equal(pkg.license, "MIT"); assert.deepEqual(pkg.repository, { type: "git", - url: "https://github.com/Wibias/github-delivery.git", + url: "git+https://github.com/Wibias/github-delivery.git", }); assert.equal(pkg.publishConfig?.access, "public"); assert.equal(pkg.publishConfig?.registry, "https://registry.npmjs.org/"); @@ -123,7 +125,7 @@ test("a real packed tarball runs --help after an offline local install", () => { packDir, ]); assert.equal(packResult.status, 0, packResult.stderr || packResult.stdout); - const [pack] = parsePackJson(packResult.stdout); + const [pack] = parseNpmPackJson(packResult.stdout); const tarball = join(packDir, pack.filename); const installResult = runNpm([