Skip to content

Stage npm releases in CI and tag successful uploads - #1022

Merged
ianmacartney merged 7 commits into
mainfrom
ian/deploy-tooling
Oct 30, 2026
Merged

ianmacartney merged 7 commits into
mainfrom
ian/deploy-tooling

Conversation

@ianmacartney

@ianmacartney ianmacartney commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

A release should be easy to start locally while still requiring a maintainer's npm 2FA approval before the package becomes public. This PR replaces laptop publishing and tag-triggered publishing with a version-bump commit → CI staging → Git tag flow.

Release flow

  1. Run npm run release from a clean checkout containing current main. It runs checks, prepares the version/changelog/lockfile, commits, and pushes to main without creating a local tag. Direct pushes and release PRs use the same CI path; neither a release-PR bot nor a merge queue is required.
  2. The command prints the commit's CI checks URL and the maintainer's npm Staged Packages URL. --watch follows that commit's Release run and opens the approval page after success when local npm whoami succeeds. Without local npm authentication it still watches CI and prints manual approval instructions. Local npm login only discovers the approval-page username; CI uses trusted publishing.
  3. CI finds the version-bump commit in the push, including a release followed by ordinary commits. Push one release at a time; multiple version bumps in a push fail with a clear error. It checks out the exact release commit, validates/builds/tests it, and uploads it with npm stage publish --provenance. The planner uses npm's semver validator, and staging requires nonempty notes in the current version's changelog section. Stable versions use latest; prerelease identifiers select alpha, beta, etc., with next for identifiers npm treats as version ranges.
  4. A single release.yml runs plan → build/pack → stage → tag, without a matrix or reusable workflow. Installation, build, tests, and packing have no OIDC permission. The staging job downloads that run’s immutable tarball artifact and uploads it with scripts disabled; it does not install dependencies or execute package code. The tag job creates npm/<version> at the source commit after staging succeeds. This job has GitHub write permission but performs no checkout or execution of package code. An existing matching tag is accepted; a conflicting tag is never moved.
  5. A maintainer reviews and approves the staged version on npm with 2FA. That is the final step—there is no polling, delayed-tagging workflow, or follow-up Git operation.

The Git tag records successful staging, not approval or public availability. A rejected candidate can keep its tag. This deliberately avoids coupling Git bookkeeping to a later interactive approval. If we eventually choose direct publishing, the upload command and npm trust permissions change; the commit and tagging flow stays the same.

Why enforce staging in npm?

Changing the workflow command alone would not protect against a rogue push that changes it back. Before enabling this flow, configure the npm trusted publisher for convex/convex-helpers, workflow release.yml, to allow staged publishing only, disable direct publishing, and remove any CI credentials that bypass approval. npm then enforces the human approval boundary even if workflow code changes. GitHub's tag rules must also permit Actions to create npm/* tags.

The workflow uses actions/setup-node with .nvmrc (Node 24.18.0), whose bundled npm 11.16.0 supports staged publishing. No separate npm installation is needed.

The approval page is https://www.npmjs.com/settings/<npm-username>/staged-packages; the staging log also supplies the ID for npm stage approve <stage-id>. There is no redundant npm run approve wrapper.

Retries and boundaries

  • A failed local push retains the release commit for recovery; do not bump again.
  • If staging fails, inspect the failure and rerun failed jobs. Manual dispatch accepts a version-bump commit on the selected ref, defaulting to its tip. gh workflow run release.yml --ref my-alpha-branch explicitly stages a branch release; ordinary branch pushes and opening/updating release PRs do not. The repository guard skips forks to avoid missing-trust failures. No GitHub environment is required. npm errors—including duplicate uploads—remain visible.
  • If tagging fails after staging, rerun failed jobs, preserving the successful staging job. Rerunning the entire workflow would attempt to upload the reserved version again.
  • If npm accepted an upload but CI lost the response, inspect the staged package before recovering. Approval and rejection happen on npm; existing Git tags are not rewritten.

Stack and validation

#1024 and #1039 are merged. Review/merge #1022 (this PR) → #1031. This PR retains its reviewed local release command and npm/<version> tags. #1031 will replace custom release preparation with standard Changesets commands and simplify the release helpers.

Follow-up review adjustments isolate build/pack from OIDC staging, explain the disabled cache, and allow explicit branch dispatch while keeping automatic releases limited to main. Example codegen remains checked by the existing Test and lint workflow; this release change does not add a separate codegen step.

Validation: 20 isolated release tests pass, including manual branch dispatch, earlier-commit retries, rejection of commits outside the selected ref, automatic-trigger/fork guards, artifact handoff, tag retries/conflicts, and denied tag writes. Build, lint/typechecks, all 4,128 package tests, and packed tarball checks also pass. actionlint and zizmor pass. No package was staged or published during validation.

ianmacartney commented Oct 9, 2026 •

Copy link
Copy Markdown
Member Author

Comment thread .github/workflows/release.yml Fixed
Comment thread .github/workflows/release.yml Fixed
@pkg-pr-new

pkg-pr-new Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/convex-helpers@1022

commit: 20191c9

@ianmacartney
ianmacartney marked this pull request as ready for review October 10, 2026 01:40

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread package.json Resolved
Comment thread .github/workflows/release.yml Resolved

ianmacartney commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

I may merge #1024 in first, to refactor into a single package.json and doing everything at the root.
I'm also going to stack on a PR that adds changesets, which may land later depending how it looks

@ianmacartney
ianmacartney force-pushed the ian/deploy-tooling branch 2 times, most recently from 205d457 to f07e852 Compare October 21, 2026 23:13
@ianmacartney
ianmacartney changed the base branch from main to ian/example-ui-layout October 21, 2026 23:13
@ianmacartney
ianmacartney force-pushed the ian/example-ui-layout branch from 800deac to f08410b Compare October 21, 2026 23:15
@ianmacartney
ianmacartney force-pushed the ian/deploy-tooling branch 2 times, most recently from 1de5091 to e88519f Compare October 21, 2026 23:25
@ianmacartney
ianmacartney changed the base branch from ian/example-ui-layout to main October 22, 2026 01:43
Comment thread .github/workflows/release.yml Fixed
@ianmacartney ianmacartney changed the title publish from CI via tag-triggered trusted publishing Stage npm releases in CI and tag successful uploads Oct 22, 2026
@ianmacartney
ianmacartney changed the base branch from main to ian/package.json-bundling-setup October 22, 2026 05:13

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/release.test.mjs Resolved
Comment thread .github/workflows/release.yml Resolved
Comment thread scripts/validate-release.mjs Resolved
Comment thread CONTRIBUTING.md Resolved
Comment thread scripts/release-plan.mjs Resolved

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/tag-release.test.mjs Resolved

@Nicolapps Nicolapps left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this!

I wonder whether it could be helpful to use something like the Changesets release PR feature to avoid the local step, but this is a clear improvement over what we had before

Comment on lines +69 to +72
npm ci
npm run release:validate
npm run build
npm run lint

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please consider moving this step to a separate job that doesn’t have id-token: write? This job could store the result as an artifact, which itself would be uploaded by the “stage” step. This gives us more resilience if the install/build/lint steps are maliciously changed to execute bad code. With the current implementation they could get an OIDC token

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion. done in 69533b1

Comment thread .github/workflows/release.yml Resolved
npm ci
npm run release:validate
npm run build
npm run lint

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we verify the codegen too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which codegen are you referring to? Unlike a component, I don't think it ships any convex _generated code?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we don't need to worry about generated files here

Comment thread .github/workflows/release.yml Outdated

jobs:
plan:
if: github.repository == 'convex/convex-helpers' && github.ref == 'refs/heads/main'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this, we won’t allow this release workflow to run from other branches for alphas. Is this intentional?

If it is intentional, something we could do to improve security is to make the stage step run in a separate GitHub environment that is configured to only run on the main branch, and modifying our NPM trust settings to only allow publication from this environment. This avoids attacks where someone modifies the release.yml file to remove this check

@ianmacartney
ianmacartney force-pushed the ian/package.json-bundling-setup branch from 1b4ff70 to 66bdb82 Compare October 30, 2026 00:39
ianmacartney added a commit that referenced this pull request Oct 30, 2026
…1024)

Make the repository root the publishable `convex-helpers` package, with JavaScript and declaration entrypoints in `dist/`. Keep TypeScript sources in `packages/convex-helpers/`; the Convex examples resolve imports through the root package name.

- Consolidate dependencies into root devDependencies, maintain exports explicitly (including `.js`-suffixed helper imports), remove export generation and arethetypeswrong, and compile directly into root `dist/`.
- Move CHANGELOG to the root and retain symlinks at the old README/CHANGELOG locations. The root README and example UI layout are already on main through #1039.
- Update build/watch/pack scripts, preview publishing, frontend hosting instructions, and the backend test harness for the root package.
- Adapt the existing `publish.sh` to the root package and lockfile so this PR can land independently of the new CI release flow. Trusted staging and local release orchestration are introduced separately in #1022.

Review/merge **#1024 (this PR) → #1022 → #1031**. This PR is based directly on current main. Preserve the current package version (0.1.129) and the separate Convex-example typecheck.

Validation: clean install/build, lint/typechecks, all 1,237 package tests. At the stack tip, the example UI production build and package-content checks also pass: 154 packed files, all explicit entrypoints present, and no example UI, release scripts, test files, or nested package.json shipped. No npm release was published.
@ianmacartney
ianmacartney changed the base branch from ian/package.json-bundling-setup to graphite-base/1022 October 30, 2026 03:04
@ianmacartney
ianmacartney changed the base branch from graphite-base/1022 to main October 30, 2026 03:04

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread eslint.config.mjs Resolved
@ianmacartney
ianmacartney merged commit 1815d4a into main Oct 30, 2026
8 checks passed
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.

3 participants