presets(vercel): hard link custom function dirs instead of copying#4373
presets(vercel): hard link custom function dirs instead of copying#4373RihanArfan wants to merge 4 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughIn Vercel preset copy mode change
🎯 2 (Simple) | ⏱️ ~5 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
commit: |
| * | ||
| * Entries listed in `skip` are ignored at the top level only. | ||
| */ | ||
| export async function hardLinkDir(src: string, dest: string, options: { skip?: Set<string> } = {}) { |
There was a problem hiding this comment.
Have you tried fs.cp (https://nodejs.org/api/fs.html#fspromisescpsrc-dest-options) with COPYFILE_FICLONE flag? (https://nodejs.org/api/fs.html#fspromisescopyfilesrc-dest-mode) (it should have same CoW effect but hopefully faster since we don't do recursion ourselve.)
There was a problem hiding this comment.
We could use this however it's filesystem dependant. It works on macOS, Windows Dev Drives (although not NTFS), and several other Linux ones like ZFS, BTRFS but not ext4 which I'm guessing CI will use.
There was a problem hiding this comment.
Also while it might be faster since on disk it's not copying again on supported FS fools like du can't see that so people still may perceive output as larger than hard linking.
Building the test fixture and measuring with du -sh:
- main 3.4M
- CoW 3.4M
- hard linking 1.0M
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 `@src/presets/vercel/utils.ts`:
- Around line 640-643: The copy logic in the preset utility is using fsp.cp with
COPYFILE_FICLONE, which only gives CoW reflinks or falls back to regular copies
instead of preserving hard-link behavior. Update the server-to-function copy
path in the utility that handles the .vc-config.json filter to use the
hard-linking implementation instead of fsp.cp, while keeping the existing
exclusion for .vc-config.json.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5f8eedea-3546-44f0-ac5a-76e0833d5ef3
📒 Files selected for processing (1)
src/presets/vercel/utils.ts
| await fsp.cp(serverDir, funcDir, { | ||
| recursive: true, | ||
| mode: constants.COPYFILE_FICLONE, | ||
| filter: (src) => basename(src) !== ".vc-config.json", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== utils.ts around lines 600-700 ==\n'
sed -n '600,700p' src/presets/vercel/utils.ts | cat -n
printf '\n== search for hard-link / cp / COPYFILE_FICLONE / vc-config ==\n'
rg -n "COPYFILE_FICLONE|hard-?link|\.vc-config\.json|fsp\.cp|copyFile|link\(" src/presets/vercel -S
printf '\n== file list around vercel preset utils ==\n'
git ls-files src/presets/vercelRepository: nitrojs/nitro
Length of output: 1978
🌐 Web query:
Node.js fs.cp COPYFILE_FICLONE fallback copyFile reflink hard link documentation
💡 Result:
In Node.js, the fs.cp and fs.copyFile functions provide support for copy-on-write (CoW) reflinks via specific constants in fs.constants [1][2][3]. When using fs.constants.COPYFILE_FICLONE as a mode, the operation attempts to create a copy-on-write reflink [1][3]. If the underlying platform or filesystem does not support copy-on-write, Node.js automatically falls back to a traditional copy mechanism (reading from the source and writing to the destination) [1][4][5]. Alternatively, fs.constants.COPYFILE_FICLONE_FORCE can be used if you require the operation to fail explicitly if copy-on-write is not supported [1][3]. It is important to distinguish this from "hard links." Reflinks (copy-on-write clones) share the same underlying data blocks on the storage media until one of the files is modified; they are distinct file entries. Hard links, by contrast, are multiple directory entries pointing to the same inode. COPYFILE_FICLONE specifically targets CoW functionality where available, not the creation of hard links [1][4]. Note that while fs.cp copies entire directory structures, its mode option passes the value directly to the underlying fs.copyFile operation for individual files [1][2][6]. The default value for mode is 0, meaning no special copy behavior (like CoW) is applied by default [1][7]. Top results: [1][4][3][5]
Citations:
- 1: https://nodejs.org/api/fs.html
- 2: https://nodejs.org/docs/latest-v24.x/api/fs.html
- 3: https://nodejs.org/docs/latest-v22.x/api/fs.html
- 4: nodejs/node@a16d88d
- 5: https://stackoverflow.com/questions/71629903/node-js-why-we-should-use-copyfile-ficlone-and-copyfile-ficlone-force-what-is
- 6: https://nodejs.org/api/fs.md
- 7: fs: Change the default value of
modeargument offs.copyFile()tofs.constants.COPYFILE_FICLONEnodejs/node#47861
Restore the hard-link copy path here
fsp.cp(..., { mode: constants.COPYFILE_FICLONE }) makes CoW reflinks when available and falls back to a regular copy when they aren’t, so it does not preserve the hard-link behavior this change is meant to add. Keep the .vc-config.json filter, but use the hard-linking path instead of cp here.
🤖 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 `@src/presets/vercel/utils.ts` around lines 640 - 643, The copy logic in the
preset utility is using fsp.cp with COPYFILE_FICLONE, which only gives CoW
reflinks or falls back to regular copies instead of preserving hard-link
behavior. Update the server-to-function copy path in the utility that handles
the .vc-config.json filter to use the hard-linking implementation instead of
fsp.cp, while keeping the existing exclusion for .vc-config.json.

❓ Type of change
📚 Description
Instead of copying __server.func when there is modifications to .vc-config.json, we now hard link all files to it. We cannot symlink within a functions dir to another since on Vercel the builder cannot resolve the files, which is why we recursively hard link.
📝 Checklist