fix: handle EEXIST race in peer dependency symlink creation

This commit is contained in:
saberzero1
2026-04-27 22:34:31 +02:00
parent 886bf6d7f6
commit 9fcc3f260c
2 changed files with 22 additions and 9 deletions

View File

@@ -127,6 +127,15 @@ function needsBuild(pluginDir) {
* 2. All other peers → symlink to the host Quartz node_modules so plugins * 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. * 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) { function linkPeerPlugins(pluginDir) {
const pkgPath = path.join(pluginDir, "package.json") const pkgPath = path.join(pluginDir, "package.json")
if (!fs.existsSync(pkgPath)) return if (!fs.existsSync(pkgPath)) return
@@ -134,16 +143,13 @@ function linkPeerPlugins(pluginDir) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
const peers = pkg.peerDependencies ?? {} const peers = pkg.peerDependencies ?? {}
// Locate the host Quartz node_modules (two levels up from .quartz/plugins/<name>)
const quartzRoot = path.resolve(pluginDir, "..", "..", "..") const quartzRoot = path.resolve(pluginDir, "..", "..", "..")
const hostNodeModules = path.join(quartzRoot, "node_modules") const hostNodeModules = path.join(quartzRoot, "node_modules")
for (const peerName of Object.keys(peers)) { 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("/")) const peerNodeModulesPath = path.join(pluginDir, "node_modules", ...peerName.split("/"))
if (fs.existsSync(peerNodeModulesPath)) continue if (fs.existsSync(peerNodeModulesPath)) continue
// Case 1: @quartz-community scoped packages → sibling plugin symlink
if (peerName.startsWith("@quartz-community/")) { if (peerName.startsWith("@quartz-community/")) {
const siblingPlugin = findPluginByPackageName(peerName) const siblingPlugin = findPluginByPackageName(peerName)
if (!siblingPlugin) continue if (!siblingPlugin) continue
@@ -152,15 +158,13 @@ function linkPeerPlugins(pluginDir) {
fs.mkdirSync(scopeDir, { recursive: true }) fs.mkdirSync(scopeDir, { recursive: true })
const target = path.relative(scopeDir, siblingPlugin) const target = path.relative(scopeDir, siblingPlugin)
fs.symlinkSync(target, peerNodeModulesPath, "dir") trySymlink(target, peerNodeModulesPath)
continue continue
} }
// Case 2: Other peers → resolve from host Quartz node_modules
const hostPeerPath = path.join(hostNodeModules, ...peerName.split("/")) const hostPeerPath = path.join(hostNodeModules, ...peerName.split("/"))
if (!fs.existsSync(hostPeerPath)) continue if (!fs.existsSync(hostPeerPath)) continue
// Ensure parent directory exists (for scoped packages like @napi-rs/simple-git)
const parts = peerName.split("/") const parts = peerName.split("/")
if (parts.length > 1) { if (parts.length > 1) {
const scopeDir = path.join(pluginDir, "node_modules", parts[0]) const scopeDir = path.join(pluginDir, "node_modules", parts[0])
@@ -170,7 +174,7 @@ function linkPeerPlugins(pluginDir) {
} }
const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath) const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath)
fs.symlinkSync(target, peerNodeModulesPath, "dir") trySymlink(target, peerNodeModulesPath)
} }
} }

View File

@@ -306,6 +306,15 @@ function findPluginByPackageName(packageName: string): string | null {
* share a single copy of packages like unified, vfile, preact, etc. * share a single copy of packages like unified, vfile, preact, etc.
* @quartz-community/* peers resolve to co-installed sibling plugins instead. * @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 { function linkPeerDependencies(pluginDir: string): void {
const pkgPath = path.join(pluginDir, "package.json") const pkgPath = path.join(pluginDir, "package.json")
if (!fs.existsSync(pkgPath)) return if (!fs.existsSync(pkgPath)) return
@@ -328,7 +337,7 @@ function linkPeerDependencies(pluginDir: string): void {
fs.mkdirSync(scopeDir, { recursive: true }) fs.mkdirSync(scopeDir, { recursive: true })
const target = path.relative(scopeDir, siblingPlugin) const target = path.relative(scopeDir, siblingPlugin)
fs.symlinkSync(target, peerNodeModulesPath, "dir") trySymlink(target, peerNodeModulesPath)
continue continue
} }
@@ -344,7 +353,7 @@ function linkPeerDependencies(pluginDir: string): void {
} }
const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath) const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath)
fs.symlinkSync(target, peerNodeModulesPath, "dir") trySymlink(target, peerNodeModulesPath)
} }
} }