Skip to content

feat(upgrade): implement fern automations upgrade action#9

Merged
lifanzou merged 11 commits intomainfrom
lifanzou/fern-upgrade-action
May 8, 2026
Merged

feat(upgrade): implement fern automations upgrade action#9
lifanzou merged 11 commits intomainfrom
lifanzou/fern-upgrade-action

Conversation

@lifanzou
Copy link
Copy Markdown
Contributor

@lifanzou lifanzou commented Apr 11, 2026

Summary

Rewrites the upgrade action as a standard Node.js action that consumes fern automations upgrade --json output from the CLI. The CLI provides all domain logic (version diffing, changelog URLs, PR formatting via the pr field), so the action is pure glue: run CLI → push → create/manage PR.

Architecture:

  • run-upgrade.ts: Uses @actions/exec.getExecOutput() to run the CLI, capturing stdout (JSON) and stderr (logs) separately. Timeout via Promise.race — no manual process lifecycle management needed. Parsing/validation extracted into parseUpgradeOutput() for direct unit testing.
  • manage-pr.ts: Clean-slate branch model: each run resets fern/upgrade to origin/<default>, applies upgrade, force-pushes. Guards that fern/ exists before proceeding. Temp directory cleanup in try/finally. Uses --force (not --force-with-lease) because the local branch has no tracking ref.
  • index.ts: Shared instrumentAction/runAction/runPostCleanup patterns matching generate, preview, verify. Validates githubToken early. Warns if CLI reports upgrades but no file changes detected (possible copy/staging bug).

Key design decisions:

  • @actions/exec over raw spawn: Eliminates timeout race conditions and interval leaks. getExecOutput() handles process lifecycle and stdio routing — timeout is a simple Promise.race.
  • --force push: The local branch is freshly created each run with no tracking ref. --force-with-lease would reject because there's no reflog to compare against.
  • version: "latest" (not "auto"): Intentional — the upgrade action should always use the newest CLI release, regardless of fern.config.json. Differs from generate which uses "auto".

Test coverage (12 tests):

  • 10 behavioral tests for parseUpgradeOutput: valid JSON, whitespace, empty stdout, invalid JSON, truncated JSON, missing cli, missing generators, non-array generators, pr: null
  • 2 pr sub-field validation tests: missing fields, empty title

Review & Testing Checklist for Human

  • Verify @actions/exec.getExecOutput() captures stdout/stderr correctly in a real GHA run
  • Verify Promise.race timeout behaves correctly (process killed on timeout, no leaks)
  • E2E: trigger upgrade workflow on a test repo and verify PR is created with correct title/body/commit
  • Verify githubToken validation fires early with a clear error when token is missing

Notes

Link to Devin session: https://app.devin.ai/sessions/0b0ff224ce294b938ff5c2f5aec943fd


Open in Devin Review

@lifanzou lifanzou self-assigned this Apr 11, 2026
@lifanzou lifanzou changed the title feat(upgrade): implement fern-upgrade GitHub Action feat(upgrade): implement fern-upgrade GitHub Action as composite Apr 20, 2026
@devin-ai-integration devin-ai-integration Bot force-pushed the lifanzou/fern-upgrade-action branch from e46063b to ae986f8 Compare April 25, 2026 15:30
@lifanzou lifanzou marked this pull request as ready for review April 25, 2026 15:59
@lifanzou lifanzou requested a review from Swimburger as a code owner April 25, 2026 15:59
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot changed the title feat(upgrade): implement fern-upgrade GitHub Action as composite feat(upgrade): composite upgrade action with CLI-driven JSON output Apr 29, 2026
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment thread actions/upgrade/action.yml Outdated
git checkout -f -B "$BRANCH" "origin/$DEFAULT_BRANCH"

# Restore upgraded fern config on top of the clean branch
cp -r "$UPGRADE_TMP/fern/"* fern/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 dotglob

Or use rsync/find-based copy that includes hidden files:

cp -a "$UPGRADE_TMP/fern/." fern/
Suggested change
cp -r "$UPGRADE_TMP/fern/"* fern/
cp -a "$UPGRADE_TMP/fern/." fern/
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Fills in the upgrade action stub with a full implementation that:
- Runs `fern automations upgrade --json` via the shared resolveFernCli helper
- Parses structured JSON output from the CLI
- Builds PR title/body/commit message from the upgrade diff
- Pushes to fern/upgrade branch and creates/updates a PR via Octokit
- Supports --include-major (default true) and version inputs
- Masks tokens via @fern-github-actions/shared

Tests: 22 tests across diff.test.ts and run-upgrade.test.ts
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
@devin-ai-integration devin-ai-integration Bot force-pushed the lifanzou/fern-upgrade-action branch from 4b14a05 to 16b36d3 Compare May 6, 2026 22:50
@devin-ai-integration devin-ai-integration Bot changed the title feat(upgrade): composite upgrade action with CLI-driven JSON output feat(upgrade): implement fern automations upgrade action May 6, 2026
devin-ai-integration Bot and others added 2 commits May 7, 2026 00:22
- Deleted diff.ts (115 lines) and diff.test.ts (286 lines) — PR
  title/body/commit message now comes from CLI's pr field
- Removed extractUpgradeJson() fallback parser — CLI stdout/stderr
  routing is clean, no need for forgiving JSON extraction
- Simplified index.ts to use json.pr directly instead of building
  from intermediate UpgradeDiff format
- Added PrSuggestion type to AutomationsUpgradeJson schema
- Updated README to reflect CLI-provided PR formatting

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…etBranch, add schemaVersion

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
lifanzou

This comment was marked as spam.

devin-ai-integration Bot and others added 8 commits May 7, 2026 02:21
…sions

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…ne compat)

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…onflict

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…ranch switch

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
… model

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…oral tests

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
…ield checks, branding

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
@lifanzou lifanzou merged commit 5c45db6 into main May 8, 2026
6 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.

2 participants