fix: handle EEXIST race in peer dependency symlink creation
This commit is contained in:
@@ -127,6 +127,15 @@ function needsBuild(pluginDir) {
|
||||
* 2. All other peers → symlink to the host Quartz node_modules so plugins
|
||||
* share a single copy of packages like unified, vfile, rehype-raw, etc.
|
||||
*/
|
||||
function trySymlink(target, linkPath) {
|
||||
try {
|
||||
fs.symlinkSync(target, linkPath, "dir")
|
||||
} catch (err) {
|
||||
if (err.code === "EEXIST") return
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function linkPeerPlugins(pluginDir) {
|
||||
const pkgPath = path.join(pluginDir, "package.json")
|
||||
if (!fs.existsSync(pkgPath)) return
|
||||
@@ -134,16 +143,13 @@ function linkPeerPlugins(pluginDir) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
||||
const peers = pkg.peerDependencies ?? {}
|
||||
|
||||
// Locate the host Quartz node_modules (two levels up from .quartz/plugins/<name>)
|
||||
const quartzRoot = path.resolve(pluginDir, "..", "..", "..")
|
||||
const hostNodeModules = path.join(quartzRoot, "node_modules")
|
||||
|
||||
for (const peerName of Object.keys(peers)) {
|
||||
// Check if this peer is already satisfied (e.g. installed as a regular dep)
|
||||
const peerNodeModulesPath = path.join(pluginDir, "node_modules", ...peerName.split("/"))
|
||||
if (fs.existsSync(peerNodeModulesPath)) continue
|
||||
|
||||
// Case 1: @quartz-community scoped packages → sibling plugin symlink
|
||||
if (peerName.startsWith("@quartz-community/")) {
|
||||
const siblingPlugin = findPluginByPackageName(peerName)
|
||||
if (!siblingPlugin) continue
|
||||
@@ -152,15 +158,13 @@ function linkPeerPlugins(pluginDir) {
|
||||
fs.mkdirSync(scopeDir, { recursive: true })
|
||||
|
||||
const target = path.relative(scopeDir, siblingPlugin)
|
||||
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||
trySymlink(target, peerNodeModulesPath)
|
||||
continue
|
||||
}
|
||||
|
||||
// Case 2: Other peers → resolve from host Quartz node_modules
|
||||
const hostPeerPath = path.join(hostNodeModules, ...peerName.split("/"))
|
||||
if (!fs.existsSync(hostPeerPath)) continue
|
||||
|
||||
// Ensure parent directory exists (for scoped packages like @napi-rs/simple-git)
|
||||
const parts = peerName.split("/")
|
||||
if (parts.length > 1) {
|
||||
const scopeDir = path.join(pluginDir, "node_modules", parts[0])
|
||||
@@ -170,7 +174,7 @@ function linkPeerPlugins(pluginDir) {
|
||||
}
|
||||
|
||||
const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath)
|
||||
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||
trySymlink(target, peerNodeModulesPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,15 @@ function findPluginByPackageName(packageName: string): string | null {
|
||||
* share a single copy of packages like unified, vfile, preact, etc.
|
||||
* @quartz-community/* peers resolve to co-installed sibling plugins instead.
|
||||
*/
|
||||
function trySymlink(target: string, linkPath: string): void {
|
||||
try {
|
||||
fs.symlinkSync(target, linkPath, "dir")
|
||||
} catch (err: unknown) {
|
||||
if ((err as NodeJS.ErrnoException).code === "EEXIST") return
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function linkPeerDependencies(pluginDir: string): void {
|
||||
const pkgPath = path.join(pluginDir, "package.json")
|
||||
if (!fs.existsSync(pkgPath)) return
|
||||
@@ -328,7 +337,7 @@ function linkPeerDependencies(pluginDir: string): void {
|
||||
fs.mkdirSync(scopeDir, { recursive: true })
|
||||
|
||||
const target = path.relative(scopeDir, siblingPlugin)
|
||||
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||
trySymlink(target, peerNodeModulesPath)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -344,7 +353,7 @@ function linkPeerDependencies(pluginDir: string): void {
|
||||
}
|
||||
|
||||
const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath)
|
||||
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||
trySymlink(target, peerNodeModulesPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user