Skip to content

fix: sync json output of pack and publish - #9247

Merged
wraithgar merged 1 commit into
latestfrom
gar/pack-json
Apr 16, 2026
Merged

wraithgar merged 1 commit into
latestfrom
gar/pack-json

Conversation

@wraithgar

@wraithgar wraithgar commented Apr 16, 2026 •

Copy link
Copy Markdown
Contributor

BREAKING CHANGE: the --json output of npm pack and npm publish have changed. They are now always consistent, and in the same format.

Previously, npm pack would output an array of entries and npm publish an object. The npm publish object also changed forms depending on if workspaces were being published.

Now, the output is always an object with the package name as the top level index.

fixes npm/statusboard#1073

BREAKING CHANGE: the --json output of `npm pack` and `npm publish` have changed.  They are now always consistent, and in the same format.

Previously, `npm pack` would output an array of entries and `npm publish` an object.  The `npm publish` object also changed forms depending on if workspaces were also being published.

Now, the output is always an object with the package name as the top level index.
@wraithgar
wraithgar requested a review from a team as a code owner April 16, 2026 18:16
@wraithgar
wraithgar merged commit 2e9b26e into latest Apr 16, 2026
22 checks passed
@wraithgar
wraithgar deleted the gar/pack-json branch April 16, 2026 20:53
reggi pushed a commit that referenced this pull request Apr 21, 2026
BREAKING CHANGE: the --json output of `npm pack` and `npm publish` have
changed. They are now always consistent, and in the same format.

Previously, `npm pack` would output an array of entries and `npm
publish` an object. The `npm publish` object also changed forms
depending on if workspaces were being published.

Now, the output is always an object with the package name as the top
level index.

fixes npm/statusboard#1073
reggi added a commit that referenced this pull request May 20, 2026
`npm stage download <id> --json` currently emits the package contents
under a literal `"undefined"` key because `logTar` is called without a
`key` option.

### Before

```json
{
  "undefined": {
    "name": "polo-meow-meow-meow",
    "version": "1.0.3",
    ...
  }
}
```

### After

```json
{
  "polo-meow-meow-meow": {
    "name": "polo-meow-meow-meow",
    "version": "1.0.3",
    ...
  }
}
```

This matches the JSON shape of `npm publish --json` and `npm pack
--json`.

### Background

The `key == null` fallback in `lib/utils/tar.js` (that would have
rendered a bare object when no key was passed) was removed from `latest`
in #9247 ("fix: sync json output of pack and publish") as a `BREAKING
CHANGE`. Per that PR:

> BREAKING CHANGE: the --json output of `npm pack` and `npm publish`
have changed. They are now always consistent, and in the same format.
>
> Previously, `npm pack` would output an array of entries and `npm
publish` an object. The `npm publish` object also changed forms
depending on if workspaces were being published.
>
> Now, the output is always an object with the package name as the top
level index.

When #9201 (npm stage) landed, it added a new `logTar` caller in
`lib/commands/stage/download.js` that did not pass a `key`, silently
violating the v12 contract established in #9247 and producing the
`"undefined"` wrapper. This PR brings the new caller into compliance.

### Repro

```
npm stage download <stage-id> --json
```

A follow-up backports this to `release/v11` for consistent output across
branches: #9381.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
reggi added a commit that referenced this pull request May 20, 2026
Backports #9380 to `release/v11` to keep `npm stage download --json`
output consistent across branches.

On `release/v11`, the `key == null` guard in `lib/utils/tar.js` was
emitting the bare object (no wrapper key). This change adds a key so the
output matches `latest` and aligns with `npm publish --json` / `npm pack
--json`.

### Before (on release/v11)

```json
{
  "name": "polo-meow-meow-meow",
  "version": "1.0.3",
  ...
}
```

### After

```json
{
  "polo-meow-meow-meow": {
    "name": "polo-meow-meow-meow",
    "version": "1.0.3",
    ...
  }
}
```

### Background

The `key == null` fallback in `lib/utils/tar.js` (still present on
`release/v11`) was removed from `latest` in #9247 ("fix: sync json
output of pack and publish") as a `BREAKING CHANGE`. Per that PR:

> BREAKING CHANGE: the --json output of `npm pack` and `npm publish`
have changed. They are now always consistent, and in the same format.
>
> Previously, `npm pack` would output an array of entries and `npm
publish` an object. The `npm publish` object also changed forms
depending on if workspaces were being published.
>
> Now, the output is always an object with the package name as the top
level index.

That breaking change was intentionally not backported to v11
(maintenance branch), so the fallback still exists here. This backport
keeps the call-site contract aligned across branches so consumers get
the same `{ "<pkg>": {...} }` shape on both.

Clean cherry-pick of 9f7834d from #9380.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
reggi added a commit that referenced this pull request Jul 10, 2026
npm pack --json now emits an object keyed by package name (#9247)
instead of an array, so the release integration workflow's
jq -r .[0].filename fails with 'Cannot index object with number'.
Parse the filename from the object instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 91528ac3-495a-4617-9630-c31478a0691f
reggi added a commit that referenced this pull request Jul 10, 2026
## Problem

The `build nodejs` jobs in `Release Integration / publish` fail at
`.github/workflows/node-integration.yml`:

```
node . pack --loglevel=silent --json | jq -r .[0].filename
→ jq: error (at <stdin>:9859): Cannot index object with number
→ Process completed with exit code 5
```

As of #9247 (sync json output of pack and publish), `npm pack --json` no
longer outputs an array. `logTar` now buffers `{ [tar.name]: tarball }`,
so the output is an object keyed by package name:

```json
{ "npm": { "filename": "npm-12.0.1.tgz", ... } }
```

The workflow still parsed it with `.[0].filename`, which errors on an
object. npm 12.0.x is the first release carrying this change, so the
release integration only started breaking now.

## Fix

Parse the filename from the object instead of an array index:

```diff
-npmtarball="$(node . pack --loglevel=silent --json | jq -r .[0].filename)"
+npmtarball="$(node . pack --loglevel=silent --json | jq -r 'to_entries[0].value.filename')"
```

Verified locally: returns `npm-12.0.1.tgz`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mbanderas added a commit to mbanderas/maestro-agora that referenced this pull request Aug 9, 2026
npm 11 and earlier describe a pack as an array of packages. npm 12 returns an
object keyed by package name instead (npm/cli#9247), so payload.length was
undefined and the assertion read "undefined !== 1". The check now normalizes
both shapes and asserts that a file list actually came back.

The publish workflow no longer installs npm@latest before publishing. That
step is what pulled npm 12 into a release path tested only against npm 11,
and the npm bundled with Node 24 already meets the 11.5.1 floor that trusted
publishing requires. It records the toolchain versions instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pm3XF1hsWYYLccRnVtPWhs
acburdine added a commit to TryGhost/Ghost-CLI that referenced this pull request Aug 18, 2026
fixes #2309
- npm 12 (npm/cli#9247) changed `npm pack --json` from a JSON array of packed
tarballs to an object keyed by package name. Users with npm >=12 installed
globally hit "'npm pack' did not return a tarball" (or, before 1.32.1, a cryptic
"object is not iterable") when updating Ghost, since the CLI only handled the
array shape.
- normalize both formats before reading the tarball filename.
acburdine added a commit to TryGhost/Ghost-CLI that referenced this pull request Aug 18, 2026
fixes #2309
- npm 12 (npm/cli#9247) changed `npm pack --json` from a JSON array of packed
tarballs to an object keyed by package name. Users with npm >=12 installed
globally hit "'npm pack' did not return a tarball" (or, before 1.32.1, a cryptic
"object is not iterable") when updating Ghost, since the CLI only handled the
array shape.
- normalize both formats before reading the tarball filename.
@BarrensZeppelin

Copy link
Copy Markdown

This change makes it impossible to run npm pack --json on multiple versions of the same package (npm pack --json lodash@3.0.0 lodash@4.0.0) and parse the output properly afterwards. Was that intended?

mortenbrudvik added a commit to mortenbrudvik/waveui that referenced this pull request Sep 26, 2026
npm 12 prints `npm pack --json` as an object keyed by package name
instead of an array of results (npm/cli#9247, listed as a breaking
change in the npm 12.0.0 release notes). pack() parsed from the first
`[`, which with npm 12 opens the `files` array, so JSON.parse threw
"Unexpected non-whitespace character after JSON". With npm 12.1.0 this
failed `npm run test:pack`, `npm run check:package` (attw-pack.mjs packs
through pack()) and two Vitest tests: pack-smoke.test.mjs "pack > writes
the tarball under npm publish --dry-run" and attw-pack.test.mjs.

- scripts/pack-smoke.mjs: new exported parsePackOutput(stdout). It
  starts at the first line that opens with `[` or `{` (skipping anything
  printed before the JSON), accepts the array (npm 10, 11) and the
  object (npm 12) shape, and throws a readable error for output without
  JSON or with other than one package. pack() uses it.
- scripts/__tests__/pack-smoke.test.mjs: parsePackOutput tests for both
  shapes, for lines before the JSON and for the error cases.
hussamsoft added a commit to hussamsoft/OhMyPCode that referenced this pull request Sep 26, 2026
…alse-positive provider substring check

Three unrelated, pre-existing failures surfaced during post-provider-fix
server-suite triage (786s full run, 6 failed files / 7 failed tests down
from the earlier much larger baseline). None are provider-enablement
related; fixed here since they're genuine bugs, not flaky/environmental:

- scripts/test-support/npm-registry.mts: npm >= 12 changed 'npm pack --json'
  from an array of packed-package objects to a single object keyed by
  package name (npm/cli#9247, confirmed via community sources + reproduced
  locally against the installed npm 12.1.0: 'npm pack --json' on a trivial
  fixture package returns {"pkg-name": {...}}, not [{...}]). The shared
  test fixture harness (used by packages/server's
  plugins/index.posix.test.ts and packages/cli's e2e plugin-lifecycle test)
  indexed packed[0] unconditionally and crashed with 'Cannot read properties
  of undefined'. Normalized both shapes with Array.isArray/Object.values
  rather than pinning to one npm major.

- packages/server/src/server/plugins/managed-source/npm.ts: fixed a second,
  real production bug the above fix exposed (the posix plugin test was
  crashing before reaching this code path). Applying a previously reviewed
  plugin update writes package.json's dependency as an exact tarball URL
  (dependencies: { [packageName]: target.resolved }) so the installed
  artifact can be verified byte-for-byte against what was reviewed. npm >= 12
  defaults allow-remote=none, its new install-time supply-chain hardening,
  which blocks exactly this shape (a root dependency pinned to a raw tarball
  URL) unless the URL shares a hostname with the configured registry -- a
  hostname-matching heuristic that doesn't reliably cover every registry/
  mirror setup. Added --allow-remote=root: permits fetching our own,
  already-integrity-verified root dependency, but not transitive ones,
  matching the minimum privilege this call actually needs.

- terminal/agent-hooks/claude/claude.test.ts: 'keeps provider names out of
  the generic server bootstrap' asserted source.toLowerCase().not.toContain
  (providerId) for every AGENT_HOOK_PROVIDERS key. Once 'omp' became a real
  provider id, this became a structural false positive -- 'omp' is a common
  substring of ordinary English words (component, complete, accomplish...)
  that necessarily appear in a large source file, unrelated to any actual
  provider-specific logic leaking into the generic bootstrap. Switched to a
  word-boundary regex (\bomp\b) so the check still catches what it's meant
  to (a real reference to the provider id as an identifier or string) without
  false-positiving on prose-like substrings.

Verified: index.posix.test.ts 23/23 (was crashing before even reaching the
assertion), claude.test.ts 7/7 (5 skipped, expected). Repo-wide typecheck 0,
lint 0.

Remaining 4 of the original 7 server failures (checkout-git.test.ts x2,
paseo-worktree-service.test.ts, checkout-session.test.ts) all trace to the
same still-unresolved external blocker: the stray, empty .git in the user's
home directory (C:\Users\hussa\.git), pending explicit approval to delete.
opencode-server-manager.test.ts's Windows-only npm-shim-resolution test is a
local-machine environment gap, not a code bug: this machine's globally
installed 'opencode' shim resolves to node_modules/@opencode/cli/bin/opencode.exe,
not the node_modules/opencode-ai/bin/opencode.exe the test (and the real
resolution logic) expect -- the test's own assertion message says as much
('Windows CI must install opencode-ai before server tests').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make pack --json return an object like publish --json instead of an array

3 participants