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
|
* 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user