feat(ci): flat dist layout + slash-separated release tags#19
Merged
Conversation
Restructures the release pipeline so action releases are consumable via
`uses: fern-api/actions@<action>/<version>` (no path component). Two
coupled changes:
1. Dist branch layout — `publish-dist` now puts `action.yml` and `dist/`
at the *root* of `dist/<action>` (was: under a `${ACTION}/` subdir
mirroring main's layout). The subdir was structurally redundant on a
per-action orphan branch, and dropping it lets consumers reference the
tag without a path component — the standard `owner/repo@tag` shape the
ecosystem expects (`actions/checkout`, `docker/build-push-action`, etc.).
The publish step now wipes the working tree on every release, which
also handles migration from the legacy subdir layout transparently.
2. Tag format — `<action>/<version>` (slash) instead of `<action>@<version>`
(at-sign), everywhere: validate, build env `RELEASE_TAG`, sentry-release
version, publish-dist tag, alias-tags major/minor, github-release tag,
marker commit messages, and the ledger entry written by
`scripts/append-release-entry.mjs`. The at-sign in the old tag format
was unconsumable: GitHub Actions parses `owner/repo/path@ref` by
anchoring on the final `@`, so `fern-api/actions/verify-token@<tag>`
would split as path=`verify-token`, ref=`v0.0.1-test`, never finding
the actual `verify-token@v0.0.1-test` tag.
Docs updated to match (CONTRIBUTING.md, root README, preview/, setup-cli/,
sync-openapi/ READMEs). `@main` examples in upgrade/ and resolve-cli/
READMEs are unchanged — those use the source-tree layout and stay
parseable because `main` has no `@`.
The two prerelease tags created with the old format
(`verify-token@v0.0.1-test`, `preview@v0.0.1-ci-test`) remain in place
but are unconsumable; flagged in CONTRIBUTING.md's backward-compat note.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swimburger
approved these changes
May 18, 2026
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes the consumer-syntax gap introduced by PR #16 — see the failure write-up at the bottom for the concrete error that motivated this PR.
Summary
Restructures the release pipeline so action releases are consumable via
uses: fern-api/actions@<action>/<version>(no path component). Two coupled changes that share the same motivation:Flat dist layout.
publish-distnow putsaction.ymlanddist/at the root ofdist/<action>(was: under a${ACTION}/subdir mirroring main's layout). The publish step wipes the working tree on every release, which also handles migration from the legacy subdir layout transparently — the next release of each action will commit the new flat layout on top of the existing dist branch history.Slash-separated tags. Tag format is now
<action>/<version>(e.g.setup-cli/v4.1.0) instead of<action>@<version>. Applied uniformly: validate-step collision check,RELEASE_TAGbuild env,sentry-release.with.version, publish-dist git tag, alias-tags major/minor, github-release tag, marker commit messages, and the ledger entry rendered byscripts/append-release-entry.mjs.Why this combination
GitHub Actions parses
uses: owner/repo/path@refby anchoring@to the end of the string. With the old<action>@<version>tag format, consuming the action requireduses: fern-api/actions/<action>@<action>@<version>(two@s), which the parser splits on the last@— givingref=<version>, which doesn't exist as a standalone tag. Every tag the old pipeline produced was unconsumable via the documenteduses:syntax.The two fixes are coupled because:
fern-api/actions/<action>@<action>/<version>— parses cleanly but reads as the duplicated<action>because the subdir name matches the tag prefix.@tags, the consumer string becomesfern-api/actions@<action>@<version>— parses (the regex greedily backtracks to the first@when there's no/pathto bound it), but it's brittle and reads oddly.Together they give the conventional shape:
uses: fern-api/actions@setup-cli/v1, matchingactions/checkout@v4,docker/build-push-action@v5, etc.${action}/subdir<action>@<version>fern-api/actions/<action>@<action>@<version>${action}/subdir<action>/<version>fern-api/actions/<action>@<action>/<version><action>@<version>fern-api/actions@<action>@<version><action>/<version>(this PR)fern-api/actions@<action>/<version>Backward compatibility
The two prerelease tags created with the old format (
verify-token@v0.0.1-test,preview@v0.0.1-ci-test) remain in place but are unconsumable. They're prereleases, marked as such on GitHub — not worth deleting, not worth migrating. Flagged in CONTRIBUTING.md's backward-compat section.@mainexamples inupgrade/README.mdandresolve-cli/README.mdare unchanged. Those use the source-tree layout (action at<action>/) and stay parseable becausemainhas no@.Test plan
pnpm typecheck && pnpm test— all 58 shared tests + per-action tests pass (no behavior changes, only string-format edits + a restructured shell block inpublish-dist).actionlint .github/workflows/release.yml— clean.scripts/append-release-entry.mjswithACTION=verify-token VERSION=v1.0.0— produces the new slash-formattedTag:andSentry release:lines, file written toverify-token/RELEASES.mdcorrectly.verify-tokenwithversion=v0.0.2-test,prerelease=true. Verify:verify-token/v0.0.2-testis createddist/verify-tokenbranch's tip commit containsaction.yml+dist/at the root (noverify-token/subdir, no leftover files from the legacy layout)uses: fern-api/actions@verify-token/v0.0.2-testresolves end-to-end from a consumer repo (usefern-api/federico-automations-tests— it already has the smoke-test workflow ready)verify-token/RELEASES.mdledger entry on main reflects the new format (Tag: verify-token/v0.0.2-test,Sentry release: verify-token/v0.0.2-test)RELEASE_TAGconstant inside the bundle isverify-token/v0.0.2-test:git show verify-token/v0.0.2-test:dist/index.js | grep -F 'verify-token/v0.0.2-test'RELEASE_TAG(so sourcemap deobfuscation resolves).Out of scope
verify-token@v0.0.1-testunder the new format — the next real release ofverify-tokenwill produce a cleanverify-token/vX.Y.Ztag; no need to backfill.Failure that motivated this PR
A consumer workflow in
fern-api/federico-automations-testspinned touses: fern-api/actions/verify-token@v0.0.1-testerrored with:The parser stripped the
verify-tokenpath component (sincev0.0.1-testisn't a valid ref) and reported the bare repo + the truncated ref. Root cause is the@in the tag name — see the "Why this combination" section above.