mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
fix(build): tolerate same-realpath symlink / stale-typed dest in assembleStandalone (#10776)
Under heavy concurrent build I/O, the bulk .build/next/standalone -> outDir tree
copy can already have carried a prior pass's result into a
NATIVE_ASSET_ENTRIES/EXTRA_MODULE_ENTRIES dest before that entry's own copy runs
(an absolute pnpm-store symlink resolving to the exact same realpath as src, or a
stale node of a different type). fs.cpSync/fs.cp refuse to overwrite either case
even with force:true, throwing ERR_FS_CP_EINVAL ("src and dest cannot be the
same") or ERR_FS_CP_DIR_TO_NON_DIR/ERR_FS_CP_NON_DIR_TO_DIR — non-deterministically
crashing the build:release/build:cli deploy pipeline on whichever entry the race
happened to hit that run.
Adds resolvesToSamePath/clearStaleDest guards to all four copy call sites (the two
sync loops in copyNativeAssetsAndExtraModules, repairEmptyExternalPackageDirs, and
the async syncNativeAssetsToDir/syncExtraModulesToDir twins) so a dest already
pointing at src is skipped and any other stale occupant is cleared before the
fresh copy.
Co-authored-by: Markus Hartung <mail@hartmark.se>
This commit is contained in:
committed by
GitHub
parent
3c9cb21cca
commit
3d7ed7aa87
@@ -347,7 +347,10 @@ async function syncNativeAssetsToDir(projectRoot, outDir, fsImpl, log) {
|
||||
if (!(await exists(sourcePath))) continue;
|
||||
|
||||
const destinationPath = path.join(outDir, ...entry.dest);
|
||||
if (path.resolve(sourcePath) === path.resolve(destinationPath)) continue;
|
||||
// See resolvesToSamePath/clearStaleDest (sync copy path, same module) — the same
|
||||
// ERR_FS_CP_EINVAL/ERR_FS_CP_DIR_TO_NON_DIR races apply to fsImpl.cp here.
|
||||
if (resolvesToSamePath(sourcePath, destinationPath)) continue;
|
||||
clearStaleDest(destinationPath);
|
||||
|
||||
const mkdir =
|
||||
typeof fsImpl.mkdir === "function" ? fsImpl.mkdir.bind(fsImpl) : fs.mkdir.bind(fs);
|
||||
@@ -385,7 +388,8 @@ async function syncExtraModulesToDir(projectRoot, outDir, fsImpl, log) {
|
||||
if (!(await exists(sourcePath))) continue;
|
||||
|
||||
const destPath = path.join(outDir, ...entry.dest);
|
||||
if (path.resolve(sourcePath) === path.resolve(destPath)) continue;
|
||||
if (resolvesToSamePath(sourcePath, destPath)) continue;
|
||||
clearStaleDest(destPath);
|
||||
|
||||
const mkdir =
|
||||
typeof fsImpl.mkdir === "function" ? fsImpl.mkdir.bind(fsImpl) : fs.mkdir.bind(fs);
|
||||
@@ -534,6 +538,46 @@ function copyStaticAndPublic({ distDir, relDistDir, projectRoot, resolvedOutDir
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Two independent copy passes assemble a bundle: the bulk "standalone -> outDir" tree
|
||||
* copy (step 1 of assembleStandalone) can already have carried a prior entry's result
|
||||
* into `dest` (e.g. an absolute pnpm-store symlink, or a directory) BEFORE this entry's
|
||||
* own copy runs. `fs.cpSync`/`fs.cp` refuse to overwrite in two such cases even with
|
||||
* `force: true`:
|
||||
* - dest already resolves (via symlink chain) to the exact same real path as src ->
|
||||
* ERR_FS_CP_EINVAL "src and dest cannot be the same".
|
||||
* - dest exists with a different node type than src (file/symlink vs directory) ->
|
||||
* ERR_FS_CP_DIR_TO_NON_DIR / ERR_FS_CP_NON_DIR_TO_DIR.
|
||||
* Under heavy concurrent build I/O this manifested non-deterministically across
|
||||
* different EXTRA_MODULE_ENTRIES/NATIVE_ASSET_ENTRIES on every retry. Resolve both
|
||||
* cases up front: skip entirely when dest is already the right target, otherwise clear
|
||||
* whatever stale node occupies dest (via lstat, so it also removes a broken symlink)
|
||||
* so the fresh copy always lands cleanly.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {string} dest
|
||||
* @returns {boolean} true when dest already IS src's target and no copy is needed
|
||||
*/
|
||||
function resolvesToSamePath(src, dest) {
|
||||
if (path.resolve(src) === path.resolve(dest)) return true;
|
||||
if (!fsSync.existsSync(dest)) return false;
|
||||
try {
|
||||
return fsSync.realpathSync(src) === fsSync.realpathSync(dest);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @see resolvesToSamePath — clears whatever stale node sits at `dest` before a copy. */
|
||||
function clearStaleDest(dest) {
|
||||
try {
|
||||
fsSync.lstatSync(dest);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
fsSync.rmSync(dest, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy native assets (better-sqlite3 and TPROXY) and extra runtime modules/sidecars
|
||||
* (wreq-js, pino, migrations, MITM server, helper scripts, sqlite-vec platform packages, …)
|
||||
@@ -547,7 +591,8 @@ function copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir) {
|
||||
const src = path.join(projectRoot, ...asset.src);
|
||||
if (!fsSync.existsSync(src)) continue;
|
||||
const dest = path.join(resolvedOutDir, ...asset.dest);
|
||||
if (path.resolve(src) === path.resolve(dest)) continue;
|
||||
if (resolvesToSamePath(src, dest)) continue;
|
||||
clearStaleDest(dest);
|
||||
fsSync.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fsSync.cpSync(src, dest, { recursive: true, force: true });
|
||||
console.log(`[assembleStandalone] Copied native asset: ${asset.label}`);
|
||||
@@ -557,7 +602,8 @@ function copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir) {
|
||||
const src = path.join(projectRoot, ...mod.src);
|
||||
if (!fsSync.existsSync(src)) continue;
|
||||
const dest = path.join(resolvedOutDir, ...mod.dest);
|
||||
if (path.resolve(src) === path.resolve(dest)) continue;
|
||||
if (resolvesToSamePath(src, dest)) continue;
|
||||
clearStaleDest(dest);
|
||||
fsSync.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fsSync.cpSync(src, dest, { recursive: true, force: true });
|
||||
console.log(`[assembleStandalone] Synced module: ${mod.label}`);
|
||||
@@ -617,6 +663,12 @@ function repairEmptyExternalPackageDirs(projectRoot, resolvedOutDir) {
|
||||
continue;
|
||||
}
|
||||
if (!sourceStat.isDirectory()) continue;
|
||||
// See resolvesToSamePath/clearStaleDest above: bundlePkgDir can itself be a
|
||||
// symlink to sourcePkgDir's realpath whose target momentarily read as empty
|
||||
// under heavy concurrent build I/O (a transient readdirSync race, not a real
|
||||
// hollow placeholder), or a stale non-directory node from an earlier pass.
|
||||
if (resolvesToSamePath(sourcePkgDir, bundlePkgDir)) continue;
|
||||
clearStaleDest(bundlePkgDir);
|
||||
|
||||
fsSync.cpSync(sourcePkgDir, bundlePkgDir, { recursive: true, force: true });
|
||||
summary.repaired += 1;
|
||||
|
||||
Reference in New Issue
Block a user