Publish preview releases with changesets prerelease mode - #3888
Publish preview releases with changesets prerelease mode#3888graygilmore wants to merge 3 commits into
Conversation
| "updateInternalDependencies": "patch", | ||
| "ignore": ["@shopify/hydrogen-example-*", "hydrogen"] | ||
| "ignore": ["@shopify/hydrogen-example-*", "hydrogen", "@shopify/storefront-e2e"], | ||
| "format": false |
There was a problem hiding this comment.
iirc format: false only avoids formatting changelog markdown, it doesn't format pre.json
let's also add .changeset/pre.json to oxfmt.config.ts's ignorePatterns?
|
|
||
| for (const line of frontmatter.split(/\r?\n/)) { | ||
| const [, name, bump] = line.match(entryPattern) ?? []; | ||
| if (name === PREVIEW_PACKAGE && bump !== "patch") { |
There was a problem hiding this comment.
there are two problems with this guard: minor keeps 2026.10.0-preview.N on the same base; only major moves it to 2027.0.0-preview.N. Also, this regex misses valid YAML forms like quoted values, inline comments, flow mappings, and block scalars. Let's add @changesets/parse as a direct dev dependency, use parseChangesetFile, and reject only release.name === PREVIEW_PACKAGE && release.type === 'major'. Keep the package filter - Phase 4 brings over plain-SemVer siblings where a major can be legitimate.
There was a problem hiding this comment.
TIL minor won't touch 2026.10.0!
The script now uses parseChangesetFile from @changesets/parse (added as a direct dev dependency) and only rejects major for @shopify/hydrogen, keeping the package filter.
|
|
||
| - name: Test OIDC token | ||
| run: | | ||
| TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ |
There was a problem hiding this comment.
nit: this reports OIDC working for a 401 JSON response because jq -r '.value' returns the non-empty string null.
dropping this probe altogether and letting publish validate OIDC is fine imo
There was a problem hiding this comment.
Dropped the probe. This was copy/pasted from main where I have vague recollections of us having trouble with it in the past but probably not needed now.
| uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1.7.0 | ||
| with: | ||
| version: pnpm changeset version | ||
| publish: pnpm changeset publish |
There was a problem hiding this comment.
blocking: (threading) the root release script still runs changeset publish --tag preview, which the pinned CLI rejects while pre.json is in pre mode. The workflow bypasses the script, but it still reads as the supported release entrypoint and throws when used. Let's drop the explicit tag there, or remove the dead script.
There was a problem hiding this comment.
Good catch — the pinned CLI throws "Releasing under custom tag is not allowed in pre mode!" on the explicit flag. Dropped --tag preview from the script; pre.json supplies the tag now.
| run: node scripts/assert-patch-changesets.ts | ||
|
|
||
| - name: Build packages | ||
| run: pnpm run build:pkgs |
There was a problem hiding this comment.
blocking: publication isn't gated on the full CI suite. CI runs as a sibling workflow, and preview currently has empty required-check arrays, so this can publish while CI is red or still running. Let's require the actual CI check before merging the generated version PR, or run the same format, changeset validation, lint, full workspace build, typecheck, and test sequence here.
There was a problem hiding this comment.
I added the CI check to the preview branch protection required status checks, so the version PR cannot merge while CI is red or still running. I think that covers this without duplicating the whole suite inside the release job.
|
|
||
| on: | ||
| push: | ||
| branches: [preview] |
There was a problem hiding this comment.
non-blocking: (threading) this merge creates the version PR; it doesn't publish until that PR merges. Phase 0 can therefore land separately, but it must land before the generated version PR. The React Router template still needs the deploy flags that bypass the released CLI's 0.0.0-preview-* sniff, and the stale skill assertions need updating.
There was a problem hiding this comment.
Good call. I'm going to fire up another PR for this.
Edit: actually I'll do it in #3890
| "updateInternalDependencies": "patch", | ||
| "ignore": ["@shopify/hydrogen-example-*", "hydrogen"] | ||
| "ignore": ["@shopify/hydrogen-example-*", "hydrogen", "@shopify/storefront-e2e"], | ||
| "format": false |
There was a problem hiding this comment.
non-blocking: the declared 3.1.4 schema doesn't know about format, although the installed 4.0.0-next.6 runtime does and honours this value. Worth updating the schema URL so autocomplete and validation describe the config we're actually using.
| { | ||
| "name": "@shopify/hydrogen", | ||
| "version": "0.0.1", | ||
| "version": "2026.10.0-preview.0", |
There was a problem hiding this comment.
let's port main's Hydrogen changelog before the first version PR merges, otherwise 2026.10.0-preview.1 lands above the branch's unpublished 0.0.1 stub, and we'll need to retrofit the release history afterwards
There was a problem hiding this comment.
I would rather do this when it is time for the main release instead of for preview. Retrofitting is a one-file paste at that point, and preview's changelog can stay self-contained until then.
| env: | ||
| npm_config_registry: https://registry.npmjs.org/ | ||
| TURBO_TELEMETRY_DISABLED: "1" | ||
| outputs: |
There was a problem hiding this comment.
nit: these outputs aren't consumed by any job in this workflow. Fine to keep if Phase we will use them soon, but for now they do nothing
There was a problem hiding this comment.
These are intentional — the upcoming dist-preview work hooks a compile job onto this one with needs: preview-release and gates on outputs.published, so keeping them here means that PR does not have to touch this job.
|
|
||
| concurrency: | ||
| group: release-${{ github.ref_name }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
| cancel-in-progress: true | |
| cancel-in-progress: false |
release jobs shouldn't cancel each other.
A cancellation can advance past an unpublished version when a new changeset arrives, or publish to npm before the action creates the Git tag/release.
A rerun can't reliably reconstruct either split state.
Preview cuts will publish @Shopify/hydrogen@<version>-preview.<n> prereleases, with n incrementing on every cut. Changesets derives those versions from the committed .changeset/pre.json created by `changeset pre enter preview`, and `changeset publish` targets the preview npm dist-tag while the file exists. Exit prerelease mode (`changeset pre exit`) before this branch ever cuts a real release. Config adjustments that support the flow: - `format: false`: the formatter auto-detection in changesets v3 finds oxfmt in node_modules and feeds it the generated changelog markdown, which oxfmt cannot format, failing `changeset version` outright. - Ignore @shopify/storefront-e2e: it depends on @Shopify/hydrogen via the workspace protocol, so every cut would otherwise bump and changelog it as a dependent. It is private and never publishes. - `baseBranch: preview`: the changeset CLI compares against this ref to detect changed packages; main has diverged too far from this branch for that comparison to mean anything. - The `$schema` URL now matches the installed config runtime (4.0.0-next.6), which also documents the `format` option. The release script drops its explicit --tag flag because the pinned CLI refuses custom tags in pre mode; the preview dist-tag comes from pre.json instead. oxfmt now ignores pre.json since changesets owns and rewrites that file on every version run, and any style disagreement between the two tools would fail format:check on version PRs.
Preview releases were previously cut by manually dispatching main's release.yml, which checked out this branch and published a one-off 0.0.0-preview-<sha>-<timestamp> version of @Shopify/hydrogen. Pushes to preview now run the same changesets flow main uses: a version PR accumulates pending changesets, and merging it publishes @Shopify/hydrogen@<version>-preview.<n> to the preview npm dist-tag. The workflow guards against ever leaving prerelease mode: without .changeset/pre.json, changeset publish would publish a plain version to the latest dist-tag and hijack it from the real releases cut from main. Concurrent runs queue instead of cancelling. A cancellation that lands between npm publish and tag creation strands state a rerun cannot rebuild, because npm refuses to publish the same version twice.
Starting packages/hydrogen at 2026.10.0-preview.0 makes every cut a prerelease of the 2026.10.0 release itself: patch and minor changesets complete the prerelease back to the same 2026.10.0 base and increment only the counter, publishing 2026.10.0-preview.1, then 2026.10.0-preview.2, and so on. Published versions sort below the eventual real 2026.10.0. The committed -preview.0 is never published because versioning always increments first, so the first cut is preview.1. Minor changesets cannot move the base while its patch component is zero, because semver completes the prerelease instead of bumping, so only a major changeset threatens the pin (2026.10.0 to 2027.0.0). CI rejects major changesets on pull requests for early feedback, and the release workflow repeats the check before versioning as the hard stop. The check parses changeset files with @changesets/parse instead of regex so quoted values and other valid YAML forms cannot slip past it. Rejecting rather than coercing keeps the declared bump types intact for whoever resolves the failure. Assisted-By: devx/c7a922dd-b56b-4d83-9238-b9e88e1011f0
a1be598 to
f902a78
Compare
TL;DR
Pushes to
previewnow run a changesets release flow that publishes@shopify/hydrogen@2026.10.0-preview.<n>to thepreviewnpm dist-tag.What and why
Previously, cutting a preview release meant manually dispatching main's
release.yml, which checked out this branch and published a one-off0.0.0-preview-<sha>-<timestamp>version. No changelog, no version PR, no record of what shipped in each cut.This PR adds the same changesets flow main uses, adapted for this branch:
release.yml(this branch had no release workflow): on every push topreview, the changesets action either updates a[ci] preview releaseversion PR from pending changesets or, when that PR merges, publishes to npm via OIDC. The file has to keep therelease.ymlname because npm's Trusted Publishing config allows a single workflow filename, shared across branches..changeset/pre.jsonmakes every version come out as-preview.<n>and makeschangeset publishtarget thepreviewdist-tag instead oflatest.packages/hydrogenstarts at2026.10.0-preview.0. Patch changesets complete the prerelease back to the same2026.10.0base and only increment the counter, so cuts publish2026.10.0-preview.1,.2, and so on — sorting below the eventual real2026.10.0. CI and the release workflow both runscripts/assert-patch-changesets.tsto reject minor/major changesets, since those would move the base off2026.10.0.Reviewer notes
format: false(changesets v3 auto-detects oxfmt and feeds it changelog markdown, which hard-failschangeset version),@shopify/storefront-e2einignore(it's a workspace dependent of hydrogen and would get version churn on every cut), andbaseBranch: preview..changeset/pre.json,changeset publishwould push a plain version to thelatestdist-tag and hijack it from the real releases on main.-preview.0never publishes (versioning increments before each cut), so the first published version is2026.10.0-preview.1. This is intentional.preview. The changeset behavior (prerelease numbering, the oxfmt failure, dependent bumps, non-patch rejection) was all verified locally against the v3 CLI this branch uses.Companion PR that removes the old dispatch job from main: #3889