feat(upgrade): composite upgrade action with CLI-driven JSON output#9
feat(upgrade): composite upgrade action with CLI-driven JSON output#9
Conversation
Implements end-to-end fern-upgrade action that automates Fern CLI and generator version upgrades. Runs `fern upgrade` and `fern generator upgrade`, detects changes by diffing config files before/after, and opens or updates a shared PR on the `fern/upgrade` branch. - Add version-diff module for parsing fern.config.json and generators.yml - Add PR management with clean-slate branch strategy (force push) - Add PR title/body generation with changelog links from FDR - Add github-token input with default GITHUB_TOKEN fallback - Add CLI version resolution (auto/latest/inherit/specific) - Stub telemetry (FER-9668) and automation config (FER-9669) as TODOs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the Node.js action (TypeScript + tsup bundle) with a composite action to align with the repo's architectural pattern where actions are thin shell wrappers around CLI commands. - Rewrite action.yml from `using: node20` to `using: composite` - Add scripts/snapshot.js for version capture (plain JS, no npm deps) - Add scripts/diff.js for diff computation + PR content generation - Reuse resolve-cli composite action for CLI version resolution - Use gh CLI for PR management instead of @actions/github - Delete all TypeScript source, tests, dist bundle, and build config - Remove upgrade from pnpm workspace (no package.json needed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dd to fern/, fix heredoc escaping Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
e46063b to
ae986f8
Compare
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
… PR contexts Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
… to specific versions Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
The buildwithfern.com changelog anchors include the release date (e.g. #2026-04-24-561), which is not available at diff time. Link to the changelog page directly instead. Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Tests cover: computeUpgradeDiff, buildPrTitle, buildPrBody, buildCommitMessage, getChangelogUrl, getShortGeneratorName. Uses node:test runner to stay consistent with the composite action pattern (no package.json, no vitest dependency). Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Adds ::add-mask:: for both tokens as the first step, matching the pattern used by the preview action (core.setSecret). Prevents tokens from leaking in CLI error output or stack traces. Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
- Use console.error instead of console.log for warnings in snapshot.js to prevent stdout corruption when captured via $(node ...) - Fix include-major description: was 'When false (default)' but actual default is 'true' - Update CONTRIBUTING.md: reclassify upgrade/ as composite action Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Avoids direct ${{ inputs.include-major }} interpolation in shell,
which is a script injection anti-pattern. Passes through env: block
instead, matching the pattern used by resolve-cli, setup-cli, and
preview actions.
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Replace brittle CHANGELOG_MAP with regex derivation from the generator name pattern (fernapi/fern-<language>-*). New generators automatically get correct changelog links without code changes. Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Replace snapshot.js + computeUpgradeDiff with a single `fern automations upgrade --json` call. The CLI now handles both fern upgrade and fern generator upgrade internally and outputs structured JSON. Changes: - Delete snapshot.js (CLI handles before/after internally) - Simplify action.yml from 7 steps to 5 - Rewrite diff.js main() to read UPGRADE_JSON env var - Add cliJsonToDiff() adapter for CLI JSON → internal format - Remove computeUpgradeDiff() and generatorKey() (CLI does this) - buildPrBody() now uses changelog URLs from CLI JSON - Update all tests for new interface (27/27 pass) Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…README - Add 'Verify CLI supports automations upgrade' step that fails early with a clear error if the CLI is too old - Add error checks for non-zero exit and empty JSON output from CLI - Update README: document CLI version requirement, include-major divergence note, update 'How it works' for new CLI-based flow Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
| git checkout -f -B "$BRANCH" "origin/$DEFAULT_BRANCH" | ||
|
|
||
| # Restore upgraded fern config on top of the clean branch | ||
| cp -r "$UPGRADE_TMP/fern/"* fern/ |
There was a problem hiding this comment.
🟡 Shell glob * silently skips dotfiles when restoring upgraded fern config
On line 144, cp -r "$UPGRADE_TMP/fern/"* fern/ uses a shell glob that does not match dotfiles (files starting with .) by default in bash. The preceding cp -r fern/ "$UPGRADE_TMP/fern" on line 137 copies ALL files (including dotfiles) from the upgraded state. After switching to the clean branch, only non-dotfile entries are restored. If fern automations upgrade ever creates or modifies a dotfile at the top level of the fern/ directory (e.g., .fernignore), those changes would be silently lost in the PR.
Fix options
Either enable dotglob before the copy:
shopt -s dotglob
cp -r "$UPGRADE_TMP/fern/"* fern/
shopt -u dotglobOr use rsync/find-based copy that includes hidden files:
cp -a "$UPGRADE_TMP/fern/." fern/| cp -r "$UPGRADE_TMP/fern/"* fern/ | |
| cp -a "$UPGRADE_TMP/fern/." fern/ | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Converts the
upgradeaction from a Node.js action (dist/index.js) to a composite action that delegates the heavy lifting to the Fern CLI's newfern automations upgrade --jsoncommand (fern-api/fern#15537).The action runs a single CLI call that internally executes both
fern upgrade(CLI version) andfern generator upgrade(generator versions), returning structured JSON output. A thin JS helper (diff.js) converts this JSON into PR title, body, and commit message.How it works
Customer-configurable inputs
versionlatestfern-tokenFERN_TOKENenv var for authinclude-majortrue--include-majorflag on the CLI commandgithub-tokengithub.tokenghCLIKey design decisions
include-majordefaults totrue(intentional divergence from CLI default offalse) — the upgrade action should aggressively upgrade, letting the preview action validatefern/upgradeto latest main HEAD viagit checkout -B::add-mask::applied before any CLI commandsfern automations upgradeFiles
action.yml— 6-step composite action (mask → resolve CLI → set run ID → verify CLI → run upgrade → format PR → push)scripts/diff.js—cliJsonToDiff()adapter + PR formatting (buildPrTitle,buildPrBody,buildCommitMessage)scripts/diff.test.js— 27 unit tests (all passing)README.md— Usage docs with CLI version requirementsRemoved
scripts/snapshot.js— replaced by CLI's internal before/after trackingdist/index.js,src/index.ts,package.json,tsconfig.json,tsup.config.ts— old Node.js actionReview & Testing Checklist for Human
fern automations upgrade --jsonfrom fern-api/fern#15537. The CLI version published must include that command before this action is used in production.UPGRADE_JSONenv var handling: The JSON is passed viaGITHUB_ENVheredoc — check that multi-line JSON doesn't break the delimiter (theUPGRADEJSONEOFterminator).Notes
getChangelogUrl()function is kept as a fallback indiff.jsfor cases where the CLI doesn't provide a changelog URL (e.g., unrecognized generators). The CLI provides changelog URLs for known generators. This intentional duplication provides resilience — if the CLI changes its output format, the GHA still produces valid changelog links.Link to Devin session: https://app.devin.ai/sessions/0b0ff224ce294b938ff5c2f5aec943fd