Skip to content

Add an npm rollback path to the release workflow #18

Description

@chris-peterson

Context

git-fi ships to npm as @gettyimages/git-fi. Publishing a GitHub Release triggers .github/workflows/release.yml, which derives the version from the tag, bumps package.json, prepends the release body to CHANGELOG.md, publishes to npm, and pushes a Release vX.Y.Z commit back to main.

There is no path back. If a release turns out to be broken, npm will never let that version number be reused, and npm unpublish is only unrestricted for 72 hours after publish (after that it needs no dependents, under 300 weekly downloads, and a single owner, or a support request). Deleting the version is also the wrong instinct: it breaks lockfiles that already pin it.

The npm-native fix is to move the latest dist-tag back to the last good version and deprecate the bad one, so npm install @gettyimages/git-fi resolves to something that works while anyone pinned to the broken version gets a warning telling them where to go. Today that means a maintainer running the commands by hand from a laptop that happens to be logged into npm, at exactly the moment they are least likely to get them right.

Proposed approach

Add a rollback path to release.yml behind workflow_dispatch. The dispatch trigger currently runs a packaging dry-run with no inputs, so it becomes a switch on an action input:

on:
  workflow_dispatch:
    inputs:
      action:
        type: choice
        options: [dry-run, rollback-latest, deprecate]
        default: dry-run
      version:
        description: Version to act on, e.g. 1.2.3
      rollback_to:
        description: Version to point `latest` at
      message:
        description: Deprecation message

rollback-latest runs npm dist-tag add @gettyimages/git-fi@<rollback_to> latest; deprecate runs npm deprecate @gettyimages/git-fi@<version> "<message>". Both are reversible, which is why they are the two worth automating.

The obstacle is authentication. The workflow publishes through npm trusted publishing: id-token: write, npm@latest, --provenance, and no NODE_AUTH_TOKEN anywhere. The OIDC exchange appears to happen inside npm publish itself, which would mean dist-tag and deprecate fall back to needing a conventional write credential. That has not been confirmed against npm's behavior, and it decides the shape of the whole change, so it is the first thing to settle.

If a token is required, it should be a granular access token scoped to @gettyimages/git-fi alone with read-and-write permission and an expiry, stored as NPM_TOKEN. A classic automation token would also work; both skip the 2FA one-time-password prompt that makes a classic publish token unusable in a runner.

Acceptance criteria

  • Whether npm trusted publishing covers npm dist-tag and npm deprecate is established by testing it, not inferred, and the answer is recorded in the workflow comments.
  • A maintainer can move latest to a prior published version from the GitHub Actions UI without holding npm credentials themselves.
  • A maintainer can deprecate a published version with a custom message from the same place.
  • The existing dry-run remains the default dispatch action, so an unattended dispatch cannot publish or mutate anything.
  • Any credential the workflow gains is scoped to this one package and cannot publish other @gettyimages packages.
  • README.md or CONTRIBUTING.md says what to do when a release is bad, including that the fix ships as a new patch version rather than a republish.

Considerations

Reintroducing a long-lived secret. Trusted publishing exists to remove the standing NPM_TOKEN from the repo. Adding one back for rollback gives it up for a path that runs rarely. Worth weighing whether rollback is better left as a documented manual procedure, with the workflow change limited to whatever OIDC does cover.

unpublish is deliberately absent above. It is irreversible and its blast radius is the whole package. Keeping it off the dispatch menu means it needs a human with credentials, which matches how often it is the right answer.

main drifts from latest. The release workflow commits Release vX.Y.Z with the bumped package.json. Moving latest back does not undo that, so main would claim 1.2.3 while installers get 1.2.2. Fine if the fix ships promptly as 1.2.4; confusing if the rollback sits there. Decide whether the rollback action should also revert the version commit, or whether the documented procedure just says to follow it with a patch release.

Who can dispatch. Anyone with write access to the repo can run a workflow_dispatch, so the rollback actions inherit that audience rather than the narrower set who hold npm publish rights today.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions