Skip to content

fix(cli): run Windows post-processing on cached bundles + add docs preview smoke test#14765

Open
thesandlord wants to merge 21 commits intomainfrom
devin/1775679517-fix-windows-cached-bundle
Open

fix(cli): run Windows post-processing on cached bundles + add docs preview smoke test#14765
thesandlord wants to merge 21 commits intomainfrom
devin/1775679517-fix-windows-cached-bundle

Conversation

@thesandlord
Copy link
Copy Markdown
Contributor

@thesandlord thesandlord commented Apr 8, 2026

Description

Refs: fern-api/fern-platform#9546, fern-api/fern-platform#9569

Two related changes:

  1. Windows cached-bundle fix: On Windows, fern docs dev skips bundle post-processing when the etag matches (cached bundle). This means bundles that were pre-extracted externally (e.g. by CI, or by a previous non-Windows-aware CLI version) never get the symlink-replacement pnpm install, causing the standalone server to fail.

  2. Docs preview smoke test: Adds a sibling smoke test to the one in fern-platform. While fern-platform's test builds the docs bundle locally and tests with the prod CLI, this test builds the CLI locally and tests with the prod docs bundle from S3. Runs on ubuntu-latest, macos-latest, and windows-latest × Node 20/22/24/26.

The root cause of Windows failures (symlinks in the bundle tar) is being fixed at the source in fern-platform#9569, which dereferences symlinks during tar creation so the bundle is Windows-compatible. This PR's CLI-side changes handle the remaining post-processing (pnpm install, .npmrc, etc.) and add the smoke test to verify it all works.

Changes Made

Windows cached-bundle fix (downloadLocalDocsBundle.ts)

  • Extracted the inline Windows post-processing logic into a standalone postProcessWindowsBundle() function
  • Call it on the cached-bundle path (etag match → early return) in addition to the fresh-download path
  • Added a .windows-post-processed marker file to avoid re-running the expensive pnpm install on every subsequent fern docs dev invocation — the marker lives inside the standalone/ directory and is automatically cleaned up when a new bundle replaces the old one
  • Simplified the decompress symlink filter with a comment noting that fern-platform now dereferences important symlinks at the source, so remaining symlink entries are safe to skip on Windows

Build utils fix (build-utils.mjs)

  • Added loadPnpmCatalog() to parse pnpm-workspace.yaml and resolve catalog: references in package.json dependencies
  • Without this, npm install in the extracted CLI artifact fails because npm doesn't understand pnpm's catalog: protocol

Docs preview smoke test (new)

  • New workflow .github/workflows/docs-preview-smoke-test.yml that:
    • Builds the CLI prod bundle (pnpm turbo run dist:cli:prod)
    • Uploads the built cli.cjs as an artifact
    • Runs smoke tests on ubuntu-latest × macos-latest × windows-latest × Node 20/22/24/26
    • Uses the locally-built CLI to run fern docs dev (CLI downloads prod bundle from S3 automatically)
    • Runs Playwright tests against the dev server verifying all pages return 200
  • Windows-specific steps: disables/re-enables Windows Defender, installs protoc + buf via direct download
  • 300s server startup timeout to accommodate Windows bundle installation (download + pnpm install takes 3-5 min)
  • Smoke test fixtures under smoke-test/fern/ (minimal docs project with markdown pages + REST API OpenAPI spec)
  • Playwright test files under smoke-test/playwright/
  • Triggered on changes to packages/cli/docs-preview/**, packages/cli/cli/**, smoke-test/**, or the workflow itself

Current Windows smoke test status

  • ✅ Windows + Node 26: passes
  • ❌ Windows + Node 20/22/24: fail because the prod bundle on S3 still contains symlinks — will pass once fern-platform#9569 is merged and the bundle is rebuilt
  • ✅ All Ubuntu and macOS tests pass across all Node versions
  • These smoke tests are non-required checks — they don't block merge

Human Review Checklist

Windows fix

  • Marker invalidation: When a new bundle is downloaded, the old app-preview folder is renamed+deleted (line ~316–323), which removes the marker. Confirm fresh downloads always re-run post-processing.
  • Error propagation in cached path: postProcessWindowsBundle is called without a try/catch at line 232 (cached-bundle path). If it throws, the entire downloadBundle call fails — is that the desired behavior, or should it fall through gracefully?
  • Guard condition: The cached-bundle call is gated on PLATFORM_IS_WINDOWS && app, matching the existing if (app) { ... if (PLATFORM_IS_WINDOWS) nesting in the fresh-download path.
  • Symlink skip assumption: The decompress filter skips all symlinks on Windows. This relies on fern-platform#9569 to dereference the important ones (file-traced packages in .next/node_modules/) at bundle build time.

Build utils

  • Catalog parser correctness: loadPnpmCatalog() is a simple regex-based YAML parser. Verify it handles quoted keys, inline comments, and multi-catalog configs correctly for this repo's pnpm-workspace.yaml.

Smoke test

  • FERN_TOKEN secret: The workflow references secrets.FERN_TOKEN — verify this secret exists in the fern repo.
  • Page paths in pages.ts: The test assumes page slugs /welcome and /getting-started. Confirm these match what the CLI generates from the fixture's docs.yml.

Testing

  • Unit tests added/updated — no new tests; the Windows fix is verified via the smoke test CI
  • Biome lint/check passes locally
  • All required CI checks pass (compile, test, lint, biome, depcheck, seed tests, test-ete)
  • Smoke test workflow verified in CI — Ubuntu and macOS pass on all Node versions, Windows passes on Node 26
  • Windows Node 20/22/24 — blocked on fern-platform#9569 merge + bundle rebuild

Link to Devin session: https://app.devin.ai/sessions/762b6ec9085f4ec0849edcca9e133472
Requested by: @thesandlord


Open with Devin

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Build the CLI locally and test it against the production docs bundle.
This is the sibling/inverse of fern-platform's docs-bundle-smoke-test
which builds the bundle locally and tests with the prod CLI.

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
@devin-ai-integration devin-ai-integration bot changed the title fix(cli): run Windows post-processing on cached bundles fix(cli): run Windows post-processing on cached bundles + add docs preview smoke test Apr 8, 2026
devin-ai-integration bot and others added 7 commits April 8, 2026 20:44
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…age.json

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…otocol error

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
….json

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
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 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +42 to +44
} catch {
return {};
}
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.

🟡 Empty catch block silently swallows errors in loadPnpmCatalog

The catch {} block at packages/cli/cli/build-utils.mjs:42 silently discards any error from readFileSync or the YAML parsing logic — no error variable is bound, nothing is logged, and no comment explains why. If the pnpm-workspace.yaml file exists but is unreadable (permissions) or malformed, the function silently returns {}. This causes a confusing downstream error in getDependencyVersion (line 58) that says the dependency "was not found in pnpm-workspace.yaml catalog" — masking the real root cause. This violates the REVIEW.md / CLAUDE.md rule: "Never swallow errors with empty catch blocks — at minimum log the error. If you truly want to ignore it, add a comment explaining why."

Suggested change
} catch {
return {};
}
} catch (error) {
// Gracefully degrade if workspace file is missing (e.g. running outside the monorepo).
// Catalog references will still fail explicitly in getDependencyVersion().
console.warn(`Warning: could not load pnpm catalog from pnpm-workspace.yaml: ${error.message}`);
return {};
}
Open in Devin Review

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

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.

Good catch — fixed in 8b2687c. The catch block now logs a warning with the error message before returning the empty fallback.

devin-ai-integration bot and others added 7 commits April 8, 2026 21:39
…mCatalog

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…nstall

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…xtraction

The tar bundle contains 1290+ Unix symlinks that are filtered out during
extraction on Windows. pnpm install recreates pnpm-managed symlinks, but
Next.js file-traced symlinks in .next/node_modules/ are NOT recreated,
causing HTTP 500 errors on all pages.

This fix:
1. Collects symlink metadata (path + linkname) during tar extraction
2. Saves metadata to .symlinks-metadata.json in the bundle directory
3. After pnpm install, recreates missing symlinks as directory junctions
   (no admin required on Windows) or file copies

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Comment on lines +196 to +199
- name: Re-enable Windows Defender real-time monitoring
if: runner.os == 'Windows'
shell: powershell
run: Set-MpPreference -DisableRealtimeMonitoring $false
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.

Windows Defender is re-enabled too early, before the main test runs. The workflow disables Defender at line 68-71 to speed up tool installation, but then re-enables it at line 196-199 BEFORE the "Start fern docs dev server" step (line 201-250). The fern docs dev server performs a slow pnpm install on Windows (as noted in the comment on line 215: "Windows bundle installation (download + pnpm install) can take 3-5 minutes"), which will be significantly slower with Defender enabled.

Move the re-enable step to after all tests complete:

- name: Stop fern docs dev server
  if: always() && steps.node-check.outputs.available == 'true'
  shell: bash
  run: |
    if [ -n "$FERN_PID" ]; then
      kill $FERN_PID 2>/dev/null || true
    fi

- name: Re-enable Windows Defender real-time monitoring
  if: always() && runner.os == 'Windows'
  shell: powershell
  run: Set-MpPreference -DisableRealtimeMonitoring $false

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

devin-ai-integration bot and others added 5 commits April 8, 2026 22:56
…nk metadata

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…uning

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
pnpm i esbuild wipes node_modules/ including copies made before it.
New approach: backup traced packages to .traced-packages-backup/ before
pnpm operations, then restore them after all pnpm ops complete in
postProcessWindowsBundle.

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
…urce instead

Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant