From 66182c530d7142cf151d9667c79ffdb2a46b6548 Mon Sep 17 00:00:00 2001 From: Furkan Date: Tue, 21 Jul 2026 10:39:40 +0300 Subject: [PATCH] fix: support npm 12 pack metadata --- src/core/npm-package-doctor.ts | 24 +++++++++++++++++++++--- tests/npm-command.test.ts | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/core/npm-package-doctor.ts b/src/core/npm-package-doctor.ts index 627776d..fe46d41 100644 --- a/src/core/npm-package-doctor.ts +++ b/src/core/npm-package-doctor.ts @@ -55,7 +55,7 @@ export interface BuildDoctorNpmPackageReportOptions { environment?: CompatibilityEnvironment; } -interface NpmPackEntry { +export interface NpmPackEntry { filename?: string; name?: string; version?: string; @@ -66,6 +66,24 @@ interface NpmPackEntry { unpackedSize?: number; } +export function parseNpmPackOutput(stdout: string): NpmPackEntry[] { + const parsed = JSON.parse(stdout) as unknown; + + if (Array.isArray(parsed)) { + return parsed.filter( + (entry): entry is NpmPackEntry => typeof entry === "object" && entry !== null + ); + } + + if (typeof parsed === "object" && parsed !== null) { + return Object.values(parsed).filter( + (entry): entry is NpmPackEntry => typeof entry === "object" && entry !== null + ); + } + + return []; +} + interface CommandSpec { command: string; args: string[]; @@ -208,8 +226,8 @@ async function packNpmPackage(packageSpec: string, destinationPath: string): Pro maxBuffer: 10 * 1024 * 1024 } ); - const packEntries = JSON.parse(stdout) as NpmPackEntry[]; - const metadata = packEntries[0]; + const packEntries = parseNpmPackOutput(stdout); + const metadata = packEntries.find((entry) => typeof entry.filename === "string"); if (!metadata?.filename) { throw new Error(`npm pack did not return a tarball for ${packageSpec}`); diff --git a/tests/npm-command.test.ts b/tests/npm-command.test.ts index 5e395e4..7727174 100644 --- a/tests/npm-command.test.ts +++ b/tests/npm-command.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { runCli } from "../src/run-cli.js"; +import { parseNpmPackOutput } from "../src/core/npm-package-doctor.js"; function createIo() { const stdout: string[] = []; @@ -87,6 +88,25 @@ async function createPackedPluginFixture(): Promise { } describe("doctor npm command", () => { + it("normalizes npm pack JSON from npm 10 through npm 12", () => { + const entry = { + name: "doctor-npm-fixture", + version: "1.2.3", + filename: "doctor-npm-fixture-1.2.3.tgz" + }; + + expect(parseNpmPackOutput(JSON.stringify([entry]))).toEqual([entry]); + expect( + parseNpmPackOutput(JSON.stringify({ "doctor-npm-fixture": entry })) + ).toEqual([entry]); + }); + + it("rejects non-record npm pack JSON shapes", () => { + expect(parseNpmPackOutput("null")).toEqual([]); + expect(parseNpmPackOutput('"unexpected"')).toEqual([]); + expect(parseNpmPackOutput("[null, 1]")).toEqual([]); + }); + it("packs an npm package and reports preinstall plugin risk as JSON", async () => { const packageRoot = await createPackedPluginFixture(); const { io, stdout, stderr } = createIo();