Skip to content

[pull] latest from npm:latest - #181

Merged
pull[bot] merged 12 commits into
adamlaska:latestfrom
npm:latest
May 20, 2026
Merged

[pull] latest from npm:latest#181
pull[bot] merged 12 commits into
adamlaska:latestfrom
npm:latest

Conversation

@pull

@pull pull Bot commented May 20, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

reggi and others added 12 commits May 20, 2026 12:59
# Introducing `npm stage` 🎉

A new command for staged publishing — allowing package maintainers to
decouple the act of publishing from proof-of-presence (2FA), making
automated workflows more secure.

🔗
[Docs](https://github.com/npm/cli/blob/014d8b211ae4a2eb35e65a3f54cb962ca04f009f/docs/lib/content/commands/npm-stage.md)

## Why Staged Publishing?

With `npm stage publish`, an automated workflow can stage a package
version *without* a 2FA prompt. The maintainer can then review and
approve the staged package at their convenience, providing 2FA only at
the approval step. This keeps proof-of-presence in the loop while
keeping CI/CD fully automated.

## Subcommands

| Command | Description | Requires 2FA |
| --- | --- | --- |
| `npm stage publish [<package-spec>]` | Stage a package for publishing
| No |
| `npm stage list [<package-spec>]` | List all staged package versions |
No |
| `npm stage view <stage-id>` | View details of a specific staged
package | No |
| `npm stage approve <stage-id>` | Approve and publish a staged package
| Yes |
| `npm stage reject <stage-id>` | Reject and remove a staged package |
Yes |
| `npm stage download <stage-id>` | Download the staged tarball for
inspection | No |

## How It Works

1. **Stage** — CI runs `npm stage publish` using any token type (no 2FA
needed). The package version is held in a pending state, not publicly
available.
2. **Review** — Maintainer runs `npm stage list` to see pending staged
packages, and `npm stage view <id>` or `npm stage download <id>` to
inspect them.
3. **Approve or Reject** — Maintainer runs `npm stage approve <id>`
(with 2FA) to publish, or `npm stage reject <id>` to discard.

## Key Behaviors

- Staged packages share the same semver uniqueness constraint as
published packages — you can't publish a version that's already staged.
- Normal `npm publish` continues to work alongside staged publishing.
- Multiple versions of the same package can be staged concurrently.
- `npm stage publish` has full parity with `npm publish` (respects
`"private": true`, workspace support, etc).
- Tags are immutable once staged — reject and re-stage to change a tag.

## Future Work

- **Trust relationship permissions** — A follow-up PR will add granular
command permissions to `npm trust`, with `--allow-publish` and
`--allow-stage-publish` flags to control whether a trust relationship
can be used for `npm publish`, `npm stage publish`, or both.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`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>
@pull pull Bot locked and limited conversation to collaborators May 20, 2026
@pull pull Bot added the ⤵️ pull label May 20, 2026
@pull
pull Bot merged commit 4296f64 into adamlaska:latest May 20, 2026
23 of 26 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant