fix: faster dev startup on Windows with directory-level symlinks#160
fix: faster dev startup on Windows with directory-level symlinks#160rsbh wants to merge 8 commits into
Conversation
…p on Windows Replace recursive per-file symlink mirroring with single directory-level symlinks (junctions on Windows). Reduces ~1000+ filesystem ops to ~1-3. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughContent mirroring now links complete content directories instead of individual files, with symlink-aware cleanup. Vite discovers SSR runtime dependency directories for filesystem access, package tooling is updated, and tests and CI add mirror, server-content, and package-test coverage. ChangesContent mirror and SSR runtime support
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/chronicle/src/cli/utils/scaffold.ts (1)
54-92: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winFix Windows
EPERMcrash by using nativefs.rm.The manual recursive symlink cleanup will fail on Windows because Node.js's
fs.unlink()throws anEPERMerror when attempting to delete a directory symlink or junction. Additionally, callingfs.lstat()in the loop is redundant sincefs.readdirwithwithFileTypes: truereturnsDirentobjects that natively provideisSymbolicLink()andisDirectory().In modern Node.js,
fs.rm(path, { recursive: true, force: true })natively and safely removes directory trees, including symlinks and junctions, without traversing into their targets. It also implicitly handles missing paths (ENOENT). You can replace this entire block with a single native call to prevent the Windows crash and simplify the code.♻️ Proposed fix to simplify and fix cleanup
-async function removeMirror(mirrorRoot: string): Promise<void> { - try { - const stat = await fs.lstat(mirrorRoot); - if (stat.isSymbolicLink() || stat.isFile()) { - await fs.unlink(mirrorRoot); - } else if (stat.isDirectory()) { - const entries = await fs.readdir(mirrorRoot, { withFileTypes: true }); - for (const entry of entries) { - const entryPath = path.join(mirrorRoot, entry.name); - const entryStat = await fs.lstat(entryPath); - if (entryStat.isSymbolicLink()) { - await fs.unlink(entryPath); - } else if (entryStat.isDirectory()) { - await cleanDirSymlinks(entryPath); - await fs.rm(entryPath, { recursive: true, force: true }); - } else { - await fs.unlink(entryPath); - } - } - await fs.rm(mirrorRoot, { recursive: true, force: true }); - } - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return; - throw error; - } +async function removeMirror(mirrorRoot: string): Promise<void> { + await fs.rm(mirrorRoot, { recursive: true, force: true }); } - -async function cleanDirSymlinks(dir: string): Promise<void> { - const entries = await fs.readdir(dir, { withFileTypes: true }); - for (const entry of entries) { - const entryPath = path.join(dir, entry.name); - const entryStat = await fs.lstat(entryPath); - if (entryStat.isSymbolicLink()) { - await fs.unlink(entryPath); - } else if (entryStat.isDirectory()) { - await cleanDirSymlinks(entryPath); - } - } -}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/cli/utils/scaffold.ts` around lines 54 - 92, Replace the manual cleanup logic in removeMirror, including cleanDirSymlinks, with a single fs.rm call using recursive and force options. Remove the redundant lstat, readdir, and unlink traversal so directory symlinks and junctions are handled safely on Windows while missing paths remain ignored.
🧹 Nitpick comments (1)
packages/chronicle/src/cli/utils/scaffold.ts (1)
44-51: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueEnsure parent directories exist before symlinking.
If a content configuration specifies a nested output directory (e.g.,
dir: 'category/docs'),fs.symlinkwill fail with anENOENTerror because the parent directory has not been created yet in the mirror root.Consider defensively creating the parent directory of
destbefore linking to ensure robust behavior for all configurations.🛠️ Proposed defensive fix
async function linkDir(source: string, dest: string): Promise<void> { try { await fs.access(source); } catch { throw new Error(`Content directory not found: ${source}`); } + await fs.mkdir(path.dirname(dest), { recursive: true }); const type = process.platform === 'win32' ? 'junction' : 'dir'; await fs.symlink(source, dest, type); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/cli/utils/scaffold.ts` around lines 44 - 51, Update linkDir to create the parent directory of dest before calling fs.symlink, using recursive directory creation so nested output paths such as category/docs work reliably. Preserve the existing source validation and symlink type selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/chronicle/src/cli/utils/scaffold.ts`:
- Around line 54-92: Replace the manual cleanup logic in removeMirror, including
cleanDirSymlinks, with a single fs.rm call using recursive and force options.
Remove the redundant lstat, readdir, and unlink traversal so directory symlinks
and junctions are handled safely on Windows while missing paths remain ignored.
---
Nitpick comments:
In `@packages/chronicle/src/cli/utils/scaffold.ts`:
- Around line 44-51: Update linkDir to create the parent directory of dest
before calling fs.symlink, using recursive directory creation so nested output
paths such as category/docs work reliably. Preserve the existing source
validation and symlink type selection.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0cc84674-9fc0-4634-8342-813eb9da2987
📒 Files selected for processing (3)
.github/workflows/ci.ymlpackages/chronicle/src/cli/utils/scaffold.test.tspackages/chronicle/src/cli/utils/scaffold.ts
…nt dirs Rolldown's native import.meta.glob (vite 8.0.3 / rolldown 1.0.0-rc.12) does not crawl through directory symlinks at build time, so the directory-level .content mirror produced builds with zero content pages. Fixed upstream in rolldown 1.1.5 (vite 8.1.5). The dev server's JS glob was unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Health-only checks stayed green while builds shipped zero content pages; only the Docker smoke test caught it. Assert the index redirect serves a page containing content links and that the page API returns frontmatter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vite 8.1 enforces server.fs.allow for SSR module-runner imports. Nitro's dev runtime and ssr.noExternal packages load by absolute path from wherever the package manager hoisted them, which the previous allow list blocked, failing every dev SSR request with "Failed to load url ... dev-entry.mjs". Resolve those packages and their transitive runtime dependencies instead of allowing all of node_modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ange Nitro accepts rolldown ^1.1.0 while vite 8.1.5 requires ~1.1.5; fresh installs resolve the just-released rolldown 1.2.0 for nitro, mixing two rolldown copies in one dev pipeline and crashing every SSR request in vite:css-post. A direct ~1.1.5 dependency makes flat installs hoist a single compatible copy. Drop once nitro and vite agree on a range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/chronicle/src/server/vite-config.ts`:
- Around line 59-84: Update the package traversal loop to track visited packages
by resolved directory rather than the package name. Move the duplicate check
until after resolution succeeds, use the existing dirs Set to skip directories
already processed, and remove or replace the seen name-based tracking so
alternate versions of the same dependency are traversed and added.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aa82ae19-293a-4310-9b67-963fec4be2a4
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
packages/chronicle/package.jsonpackages/chronicle/src/server/vite-config.ts
- removeMirror: replace manual symlink walk with fs.rm (doesn't follow symlinks, handles junctions and missing paths) - linkDir: create parent dirs so nested content dirs work - resolveRuntimeDepDirs: dedupe by resolved dir instead of package name so isolated layouts with multiple versions of a dep are all allowed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
On Windows,
chronicle devtakes a very long time to start on docs sites with thousands of MDX files. Chronicle mirrors project content by creating a symlink for every single file, and per-file symlinks are slow on Windows — so startup drags and iteration feels sluggish.Solution
Testing
🤖 Generated with Claude Code