feat(upgrade): implement fern automations upgrade action#9
Conversation
e46063b to
ae986f8
Compare
| 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.
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>
4b14a05 to
16b36d3
Compare
- 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>
…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>
Summary
Rewrites the upgrade action as a standard Node.js action that consumes
fern automations upgrade --jsonoutput from the CLI. The CLI provides all domain logic (version diffing, changelog URLs, PR formatting via theprfield), 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 viaPromise.race— no manual process lifecycle management needed. Parsing/validation extracted intoparseUpgradeOutput()for direct unit testing.manage-pr.ts: Clean-slate branch model: each run resetsfern/upgradetoorigin/<default>, applies upgrade, force-pushes. Guards thatfern/exists before proceeding. Temp directory cleanup intry/finally. Uses--force(not--force-with-lease) because the local branch has no tracking ref.index.ts: SharedinstrumentAction/runAction/runPostCleanuppatterns matchinggenerate,preview,verify. ValidatesgithubTokenearly. Warns if CLI reports upgrades but no file changes detected (possible copy/staging bug).Key design decisions:
@actions/execover rawspawn: Eliminates timeout race conditions and interval leaks.getExecOutput()handles process lifecycle and stdio routing — timeout is a simplePromise.race.--forcepush: The local branch is freshly created each run with no tracking ref.--force-with-leasewould 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 offern.config.json. Differs fromgeneratewhich uses"auto".Test coverage (12 tests):
parseUpgradeOutput: valid JSON, whitespace, empty stdout, invalid JSON, truncated JSON, missingcli, missinggenerators, non-arraygenerators,pr: nullReview & Testing Checklist for Human
@actions/exec.getExecOutput()captures stdout/stderr correctly in a real GHA runPromise.racetimeout behaves correctly (process killed on timeout, no leaks)githubTokenvalidation fires early with a clear error when token is missingNotes
Link to Devin session: https://app.devin.ai/sessions/0b0ff224ce294b938ff5c2f5aec943fd