Skip to content

fix: link peer dependencies for local-source plugins too - #2503

Open
tyrcho wants to merge 1 commit into
jackyzha0:v5from
tyrcho:fix/local-plugin-peer-deps
Open

fix: link peer dependencies for local-source plugins too#2503
tyrcho wants to merge 1 commit into
jackyzha0:v5from
tyrcho:fix/local-plugin-peer-deps

Conversation

@tyrcho

@tyrcho tyrcho commented Jul 27, 2026

Copy link
Copy Markdown

Summary

installPlugin() only calls linkPeerDependencies() for git-sourced plugins (inside buildInstalledPlugin()). Local sources (./path/to/plugin) take an earlier return after symlinking and skip that call entirely.

This means a local plugin that peer-depends on another @quartz-community/* plugin (e.g. a custom view type registering against bases-page's view registry) resolves a different module instance of that peer than the rest of Quartz uses — since @quartz-community/* peers are resolved by symlinking directly to the sibling plugin's own installed directory (see linkPeerDependencies), not through normal node_modules resolution. Without that symlink, Node falls back to whatever it finds walking up parent directories, which is either a stale copy or nothing at all.

In practice this breaks any module-level singleton state shared between the local plugin and its peer — e.g. a view-type registry: the peer's registration call runs against one module instance, while the actual renderer (instantiated by the loader) reads a different, never-populated instance, silently rendering nothing (no error, since the import itself doesn't necessarily fail).

Found while building a custom local leaflet-map view type against bases-page for a personal site — the view type registration ran, but bases-page never saw it registered.

Fix

Call linkPeerDependencies(pluginDir) for local sources too, at both the existing-symlink and freshly-linked return points in installPlugin().

Test plan

  • npx tsc --noEmit passes clean
  • Verified on a real site: after manually symlinking the peer as a workaround, a local plugin's view-type registration was correctly visible to bases-page; this fix makes that automatic

installPlugin() only calls linkPeerDependencies() for git-sourced
plugins, inside buildInstalledPlugin(). Local sources take an earlier
return (symlink only) and skip that call entirely.

This means a local plugin that peer-depends on another
@quartz-community/* plugin (e.g. a custom view type registering
against bases-page's view registry) resolves a *different* module
instance of that peer than the rest of Quartz uses — since
@quartz-community/* peers are resolved by symlinking directly to the
sibling plugin's own installed directory (see linkPeerDependencies),
not through node_modules resolution. Without that symlink, Node falls
back to whatever it finds walking up parent directories, which is
either a stale copy or nothing at all.

In practice this breaks any module-level singleton state shared
between the local plugin and its peer — e.g. a view-type registry:
the peer's registration call runs against one module instance, while
the actual renderer (instantiated by the loader) reads a different,
never-populated instance, silently rendering nothing.

Fix: call linkPeerDependencies(pluginDir) for local sources too, at
both existing-symlink and freshly-linked return points.
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
quartz ✅ Ready (View Log) Visit Preview bf026a6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant