merge: sync with upstream jackyzha0/quartz v5 (23 commits) - #18
Conversation
Plugins can now be referenced as "@quartz-community/<name>" in quartz.config.yaml and via 'quartz plugin add'. The npm path skips git installation and resolves from node_modules. Changes: - gitLoader.ts: detect @scope/name as npm package in parsePluginSource() - config-loader.ts: skip git install for npm packages, read manifests from node_modules via createRequire - install-plugins.ts: filter npm packages from prebuild, fallback to YAML config parsing to avoid loading full quartz.ts - plugin-data.js: npm detection in CLI parseGitSource() - plugin-git-handlers.js: npm install path in handlePluginAdd() - package.json: add missing hast-util-from-html dependency
- quartz.config.default.yaml: all 44 plugins use @quartz-community/* npm specifiers - cli/templates/*.yaml: all 4 templates updated (default, blog, obsidian, ttrpg) - bootstrap-cli.mjs: updated help text - package.json: devDeps use npm semver ranges instead of github: specifiers - gitLoader.ts: regeneratePluginIndex scans npm packages from node_modules - install-plugins.ts: generates plugin index for npm packages - Removed quartz.lock.json (only relevant for git-installed plugins)
With npm specifiers, quartz.lock.json may not exist. The glob pattern makes the COPY conditional.
- Added all 44 default plugins as dependencies so npm ci installs them - Fixed @quartz-community/fonts → @quartz-community/quartz-fonts (actual npm name) - Regenerated package-lock.json with plugin dependencies
og-image requires sharp as a peer dep which conflicts with the version installed by quartz core. legacy-peer-deps allows both to coexist until the peer dep ranges are aligned.
The lockfile was generated on linux x86_64 but CI needs platform- specific optional deps for all architectures. npm install resolves them correctly while npm ci requires exact lockfile match.
The npm specifier path requires additional work in the esbuild transpilation pipeline to mark npm imports as external. Reverting to github: specifiers to restore the docs site immediately. The npm loader code (parsePluginSource, config-loader, CLI) remains in place for future use. Only the default config/templates are reverted.
- Default config and all 4 templates use @quartz-community/* npm specifiers - config-loader: npm packages imported directly (not via .quartz/plugins/ paths) - gitLoader: regeneratePluginIndex cross-references .d.ts with .js to correctly classify type-only exports, preventing runtime 'does not provide export' errors - All 44 default plugins added as dependencies - Locally tested: 111 docs files → 374 output files, zero errors
…er ranges) Multiple plugins declare incompatible peer dep ranges for shared packages (sharp, @myriaddreamin/rehype-typst, etc). These are optional peers that work correctly at runtime. legacy-peer-deps is the appropriate setting for a host application consuming many plugins.
Remove unconditional import of CustomOgImagesEmitterName from .quartz/plugins in Head.tsx. This caused builds to fail when the og-image plugin was not listed in quartz.config.yaml, violating the plugin system's opt-in contract. The fix inlines the emitter name string constant at the usage site. Also convert the ContentDetails import in fileTrie.ts to `import type` since it is only used in a type position.
Brings in npm-specifier support for quartz-community plugins, alongside upstream fixes (og-image/Head.tsx decoupling, docs). Conflict resolutions: - package.json: kept our newer dependency versions (post dependabot merges), added upstream's ~44 @quartz-community/* npm deps and hast-util-from-html (already imported directly in dispatcher.ts but missing from the manifest) - package-lock.json: regenerated via `npm install`, then re-applied the immutable/brace-expansion security bumps from the prior PR - quartz/plugins/loader/install-plugins.ts: took upstream's version, which handles both git- and npm-sourced plugins (superset of ours) - quartz.lock.json: deleted (upstream removed it; no longer needed once plugins are npm-installed rather than git-cloned) Migrated our quartz.config.yaml and quartz.config.default.yaml from github: specifiers to @quartz-community/* npm specifiers for all 44 first-party plugins (github: remains supported for the one local third-party plugin, ./local-plugins/lang-switch, untouched). This eliminates the git-clone + tsup build step for these plugins during CI, removing the class of flakiness fixed earlier today (favicon/ og-image "tsup: not found") since npm packages ship pre-built. Verified: tsc --noEmit clean, prettier clean, 109/109 tests passing, full quartz build succeeds (339 files), plugin install step dropped from ~7 minutes to under 1 second.
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughQuartz migrates community plugin references from GitHub sources to scoped npm packages, adds npm plugin parsing and loading, updates configuration templates and dependencies, and changes CI, deployment, and Docker installs to use Changesnpm Plugin Migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant QuartzCLI
participant npm
participant PluginLoader
participant QuartzConfig
User->>QuartzCLI: Add or configure `@quartz-community/plugin`
QuartzCLI->>npm: Install scoped package
PluginLoader->>npm: Resolve package metadata and exports
PluginLoader->>QuartzConfig: Generate plugin index
QuartzConfig->>PluginLoader: Import npm plugin by package name
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
CodeQL flagged js/shell-command-injection-from-environment on the `quartz plugin add <npm-package>` path merged from upstream: it built a shell string from a user-supplied plugin name and ran it via execSync. Switch to execFileSync with an argv array so the plugin name is never interpreted by a shell.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
quartz/plugins/loader/gitLoader.ts (1)
982-1074: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffDuplicate export-discovery logic between the Git and npm loops.
Lines 982–1016 (Git dirs) and 1038–1074 (npm packages) are near-identical:
.d.tsparsing,dist/index.jsexport scan,named/extraTypes/typescomputation, andoverridable/passthroughclassification. Extract a shared helper (takingdistIndex,jsIndex, and a key/importPath) to avoid divergence as this logic evolves.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@quartz/plugins/loader/gitLoader.ts` around lines 982 - 1074, Extract the duplicated export-discovery and classification flow from the Git and npm loops into a shared helper that accepts the declaration path, JavaScript index path, and plugin key/import path. Have the helper perform d.ts parsing, JavaScript export scanning, type computation, and overridable/passthrough classification, then update both loops to reuse it while preserving their existing registration and nameCount behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Dockerfile`:
- Line 11: Remove the `COPY quartz.lock.json* .` instruction from the Dockerfile
so the build no longer references the deleted lockfile and can proceed to `npm
install`.
In `@quartz/bootstrap-cli.mjs`:
- Line 43: Update launchTui and the TUI installation guidance together: resolve
the runtime entry point from the installed `@quartz-community/tui` package under
node_modules, or create a compatible .quartz/plugins/tui path, so the advertised
npm command produces a path launchTui can find. Ensure the missing-plugin check
and execution path use the same resolved location before retaining this message.
In `@quartz/cli/plugin-git-handlers.js`:
- Around line 1193-1200: Prevent shell injection across the npm source flow: in
quartz/cli/plugin-git-handlers.js at lines 1193-1200, replace the interpolated
execSync npm installation with spawnSync or execFile using separate command
arguments and preserve error handling; in quartz/cli/plugin-data.js at lines
193-197, validate npm package specifiers before classifying them as npm sources,
rejecting shell metacharacters or otherwise invalid package names.
In `@quartz/cli/templates/obsidian.yaml`:
- Line 271: Update the Excalidraw plugin entry in the Obsidian template to use
the GitHub repository specifier for quartz-community/obsidian-plugin-excalidraw
instead of the `@quartz-community` npm package name. Preserve the existing source
entry structure and align it with the quartz-themes block and documented quartz
plugin add command.
In `@quartz/plugins/loader/config-loader.ts`:
- Line 450: Update the plugin name assignment in the config loader to use
spec.name directly, removing the redundant npmPackage ternary while preserving
the resulting value.
In `@quartz/plugins/loader/gitLoader.ts`:
- Around line 1018-1029: Update the npm package manifest resolution in the
npmPackages loop to use createRequire with the project root search path, passing
{ paths: [process.cwd()] } when resolving `${npmPkg}/package.json`. Keep the
existing dist/index.d.ts construction and missing-package handling unchanged,
matching the resolution approach used by config-loader.ts.
---
Nitpick comments:
In `@quartz/plugins/loader/gitLoader.ts`:
- Around line 982-1074: Extract the duplicated export-discovery and
classification flow from the Git and npm loops into a shared helper that accepts
the declaration path, JavaScript index path, and plugin key/import path. Have
the helper perform d.ts parsing, JavaScript export scanning, type computation,
and overridable/passthrough classification, then update both loops to reuse it
while preserving their existing registration and nameCount behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9d5bd35c-ac5d-4b32-b3df-aa100117551f
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (23)
.github/workflows/ci.yaml.github/workflows/templates/build-preview.yaml.github/workflows/templates/deploy-v5.yaml.npmrcDockerfiledocs/getting-started/upgrading.mdpackage.jsonquartz.config.default.yamlquartz.config.yamlquartz.lock.jsonquartz/bootstrap-cli.mjsquartz/cli/plugin-data.jsquartz/cli/plugin-git-handlers.jsquartz/cli/templates/blog.yamlquartz/cli/templates/default.yamlquartz/cli/templates/obsidian.yamlquartz/cli/templates/ttrpg.yamlquartz/components/Head.tsxquartz/plugins/loader/config-loader.tsquartz/plugins/loader/gitLoader.tsquartz/plugins/loader/install-plugins.tsquartz/plugins/loader/types.tsquartz/util/fileTrie.ts
💤 Files with no reviewable changes (1)
- quartz.lock.json
| COPY quartz/ ./quartz/ | ||
| COPY quartz.lock.json . | ||
| RUN npm ci; npx quartz plugin install | ||
| COPY quartz.lock.json* . |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Remove the deleted lockfile from the Docker copy step.
quartz.lock.json is removed in this PR, so COPY quartz.lock.json* . has no source match and the image build fails before npm install. Remove this instruction.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Dockerfile` at line 11, Remove the `COPY quartz.lock.json* .` instruction
from the Dockerfile so the build no longer references the deleted lockfile and
can proceed to `npm install`.
| console.error( | ||
| "TUI plugin not installed. Install with:\n" + | ||
| " npx quartz plugin add github:quartz-community/tui\n", | ||
| " npx quartz plugin add @quartz-community/tui\n", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Fix the TUI runtime path before advertising npm installation.
The instructed command installs under node_modules/@quartz-community/tui, while launchTui still checks .quartz/plugins/tui/dist/App.mjs. A newly installed npm TUI will therefore still be reported as missing. Resolve the installed package path (or preserve a compatible link) before updating this message.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@quartz/bootstrap-cli.mjs` at line 43, Update launchTui and the TUI
installation guidance together: resolve the runtime entry point from the
installed `@quartz-community/tui` package under node_modules, or create a
compatible .quartz/plugins/tui path, so the advertised npm command produces a
path launchTui can find. Ensure the missing-plugin check and execution path use
the same resolved location before retaining this message.
| if (parsed.npmPackage) { | ||
| const name = nameOverride ?? parsed.name | ||
| console.log(styleText("cyan", `→ Installing ${name} from npm...`)) | ||
| execSync(`npm install ${parsed.name}`, { cwd: process.cwd(), stdio: "inherit" }) | ||
| const configSource = nameOverride ? { repo: parsed.name, name: nameOverride } : parsed.name | ||
| const pluginDir = path.join(process.cwd(), "node_modules", ...parsed.name.split("/")) | ||
| addedPlugins.push({ name, pluginDir, source: parsed.name, configSource }) | ||
| continue |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C 3 'execSync\(`npm install \$\{parsed\.name\}' quartz/cli/plugin-git-handlers.js
rg -n -C 3 'startsWith\("@".*includes\("/"' quartz/cli/plugin-data.jsRepository: Faustze/notes
Length of output: 1053
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
git ls-files | rg '(^|/)plugin-(git-handlers|data)\.js$' || true
echo
echo "== plugin-git-handlers relevant section =="
sed -n '1160,1215p' quartz/cli/plugin-git-handlers.js | cat -n
echo
echo "== plugin-data relevant section =="
sed -n '170,210p' quartz/cli/plugin-data.js | cat -n
echo
echo "== parsePluginSource imports/top =="
sed -n '1,80p' quartz/cli/plugin-data.js | cat -n
echo
echo "== call sites for parsePluginSource / npmPackage handling =="
rg -n "parsePluginSource|npmPackage|pluginDir|configSource|source:" quartz/cli/plugin-git-handlers.js quartz/cli/plugin-data.jsRepository: Faustze/notes
Length of output: 24083
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
git = Path("quartz/cli/plugin-git-handlers.js").read_text()
data = Path("quartz/cli/plugin-data.js").read_text()
print("plugin-git-handlers execSync npm line:", bool(re.search(r'execSync\(`npm install \$\{parsed\.name\}', git)))
print("plugin-data npm package branch:", bool("# Handle npm scoped packages" in data and 'url.startsWith("@")' in data and 'url.includes("/")' in data and "!url.includes(":")) in data))
inputs = [
"`@scope/pkg`",
"`@scope/pkg`; echo PWNED >/tmp/pwned",
"`@scope/`@nested/pkg",
"`@scope/pkg`@1.0.0; echo PWNED >/tmp/pwned2",
"`@scope/pkg`#subdir; echo PWNED >/tmp/pwned3",
]
for value in inputs:
npm_package = (isinstance("", str) and value.startswith("@") and "/" in value and ":" not in value) and True
# approximate template used by code for npm install command
cmd = f"npm install {value}"
print(f"input={value!r} npmPackage={npm_package} command={cmd!r};shell_meta_chars={set(";!|&$`").intersection(value)}")
python_path_expr = re.compile(r'node_modules(?:/.*?)*package\.json', re.S)
match = python_path_expr.search("node_modules/@scope/package-name/package.json")
print("python path traversal safe for `@scope/package-name/package.json`:", bool(match and ".." not in match.group(0)), "path:", match.group(0) if match else None)
PY
echo
echo "== package manager config =="
for f in package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb; do
[ -f "$f" ] && echo "--- $f" && (if [ "$f" = package.json ]; then node -e "const p=require('./package.json'); console.log(JSON.stringify({engines:p.engines,workspaces:p.workspaces},null,2))"; fi)
doneRepository: Faustze/notes
Length of output: 571
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
git = Path("quartz/cli/plugin-git-handlers.js").read_text()
data = Path("quartz/cli/plugin-data.js").read_text()
print("plugin-git-handlers execSync npm line:", bool(re.search(r'execSync\(`npm install \$\{parsed\.name\}', git)))
print("plugin-data npm package branch:", bool("# Handle npm scoped packages" in data and 'url.startsWith("@")' in data and 'url.includes("/")' in data and '!url.includes(":")' in data))
inputs = [
"`@scope/pkg`",
"`@scope/pkg`; echo PWNED >/tmp/pwned",
"`@scope/`@nested/pkg",
"`@scope/pkg`@1.0.0; echo PWNED >/tmp/pwned2",
"`@scope/pkg`#subdir; echo PWNED >/tmp/pwned3",
]
for value in inputs:
npm_package = value.startswith("@") and "/" in value and ":" not in value
cmd = f"npm install {value}"
print(f"input={value!r} npmPackage={npm_package} command={cmd!r};shell_meta_chars={set(';!|&$`').intersection(value)}")
match = Path('quartz/cli/plugin-git-handlers.js').read_text() # no-op import if needed; original path was Python expression, not repo
python_path_expr = re.compile(r'node_modules(?:/.*?)*package\.json', re.S)
match = python_path_expr.search("node_modules/@scope/package-name/package.json")
print("package.json path for `@scope/package-name`:", match.group(0) if match else None)
PY
echo
echo "== package manager config =="
if [ -f package.json ]; then
echo "--- package.json"
node -e "const p=require('./package.json'); console.log(JSON.stringify({engines:p.engines,workspaces:p.workspaces,scripts:p.scripts},null,2))"
fi
for f in package-lock.json pnpm-lock.yaml yarn.lock; do
[ -f "$f" ] && echo "--- $f (first 5 lines):" && head -5 "$f"
doneRepository: Faustze/notes
Length of output: 2120
Prevent command injection in npm plugin installation.
quartz/cli/plugin-data.js classifies any @.../... without : as an npm source, and quartz/cli/plugin-git-handlers.js passes that CLI-controlled name directly into execSync("npm install ${parsed.name}"), so inputs like @scope/pkg; ... can execute arbitrary shell commands. Install through child_process.spawnSync/execFile with an argument array, and validate npm package specifiers before treating them as npm sources.
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 1196-1196: Shell command built from environment values
This shell command depends on an uncontrolled absolute path.
🪛 OpenGrep (1.25.0)
[ERROR] 1196-1196: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
📍 Affects 2 files
quartz/cli/plugin-git-handlers.js#L1193-L1200(this comment)quartz/cli/plugin-data.js#L193-L197
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@quartz/cli/plugin-git-handlers.js` around lines 1193 - 1200, Prevent shell
injection across the npm source flow: in quartz/cli/plugin-git-handlers.js at
lines 1193-1200, replace the interpolated execSync npm installation with
spawnSync or execFile using separate command arguments and preserve error
handling; in quartz/cli/plugin-data.js at lines 193-197, validate npm package
specifiers before classifying them as npm sources, rejecting shell
metacharacters or otherwise invalid package names.
Source: Linters/SAST tools
| options: | ||
| theme: default | ||
| - source: github:quartz-community/obsidian-plugin-excalidraw | ||
| - source: "@quartz-community/obsidian-plugin-excalidraw" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
npm view `@quartz-community/obsidian-plugin-excalidraw` version 2>/dev/null || echo "MISSING on npm"Repository: Faustze/notes
Length of output: 143
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate obsidian.yaml =="
git ls-files | grep -F 'obsidian.yaml' || true
echo "== package manager files =="
git ls-files | grep -E '(^|/)(package.json|pnpm-lock.yaml|package-lock.json|yarn.lock)$' || true
echo "== relevant package.json deps/search =="
for f in $(git ls-files | grep -E '(^|/)package.json$'); do
echo "--- $f"
python3 - <<'PY' "$f"
import json, sys
p=sys.argv[1]
try:
data=json.load(open(p))
except json.JSONDecodeError as e:
print("JSON_ERROR", e)
raise SystemExit
deps={}
for k in ("dependencies","devDependencies","peerDependencies","optionalDependencies"):
deps.update(data.get(k) or {})
for name in ("`@quartz-community/obsidian-plugin-excalidraw`", "obsidian-plugin-excalidraw", "quartz-themes"):
print(name, deps.get(name))
PY
done
echo "== template occurrences =="
rg -n "`@quartz-community/obsidian-plugin-excalidraw`|obsidian-plugin-excalidraw|quartz-themes|source:" quartz/cli/templates/obsidian.yaml package.json 2>/dev/null || trueRepository: Faustze/notes
Length of output: 4597
🌐 Web query:
npm package @quartz-community/obsidian-plugin-excalidraw``
💡 Result:
The @quartz-community/obsidian-plugin-excalidraw is a Quartz community plugin designed to render Obsidian Excalidraw drawings as full-page, interactive SVG visualizations within a Quartz site [1][2]. Despite the name format suggesting it might be an npm package, it is primarily distributed and installed directly via GitHub within the Quartz ecosystem [1]. You do not install it via the standard npm install command; instead, you add it to your Quartz project using the Quartz CLI [1][2]: npx quartz plugin add github:quartz-community/obsidian-plugin-excalidraw Key details about the plugin include: Functionality: It parses and renders both.excalidraw.md and.excalidraw files, supporting features like shapes, text, arrows, embedded notes (transcluded content), and embedded webpages via iframes [1][3]. It also handles dark/light theme adaptation automatically [1]. Compatibility: It is designed for use with Quartz (specifically v5) and serves as the bridge for Obsidian users to display their Excalidraw content online [4][3][5]. Configuration: Once added, it can be configured in your quartz.config.yaml file to adjust settings such as enableInteraction, darkMode, and exportPadding [1][2]. For further details or to report issues, the project is hosted on GitHub under the quartz-community organization [1].
Citations:
- 1: https://github.com/quartz-community/obsidian-plugin-excalidraw
- 2: https://github.com/quartz-community/obsidian-plugin-excalidraw/blob/main/README.md
- 3: quartz-community/obsidian-plugin-excalidraw@2272491
- 4: https://saberzero1.github.io/quartz-syncer-docs/settings/integrations/excalidraw
- 5: https://quartz.jzhao.xyz/features/obsidian-compatibility
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant obsidian.yaml lines =="
sed -n '258,274p' quartz/cli/templates/obsidian.yaml | cat -n
echo "== fetch release/dist info for quartz-community/excalidraw package if available =="
python3 - <<'PY'
import json, urllib.request
packages = [
"https://registry.npmjs.org/@quartz-community/obsidian-plugin-excalidraw",
"https://api.github.com/repos/quartz-community/obsidian-plugin-excalidraw",
]
for url in packages:
print(f"--- {url}")
try:
req = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": "CodeRabbit"})
with urllib.request.urlopen(req, timeout=20) as resp:
data = json.loads(resp.read())
if url.startswith("https://api.github.com"):
print("name", data.get("name"))
print("default_branch", data.get("default_branch"))
print("has_package_json", ".github/workflows/npm-publish.yml" in data.get("archive_submodule", "") or any("npm" in (x.get("path") or "").lower() for x in data.get("files", [])))
else:
print("latest_version", data.get("dist-tags", {}).get("latest"))
print("published", bool(data.get("time", {}).get("modified") == data.get("time", {}).get("created", {}).get("modified")))
print("keywords", data.get("keywords"))
except Exception as e:
print(type(e).__name__, str(e))
PYRepository: Faustze/notes
Length of output: 1215
🌐 Web query:
github quartz-community obsidian-plugin-excalidraw package.json
💡 Result:
The query involves two distinct GitHub repositories related to Excalidraw and Obsidian: 1. quartz-community/obsidian-plugin-excalidraw: This is a Quartz plugin designed to render Obsidian Excalidraw drawings as interactive SVG visualizations within a Quartz-generated site [1][2]. It is installed into a Quartz project via npx quartz plugin add github:quartz-community/obsidian-plugin-excalidraw [1][3]. The repository documentation indicates it is based on the quartz-community/plugin-template [1]. 2. zsviczian/obsidian-excalidraw-plugin: This is the primary Obsidian plugin repository for editing and viewing Excalidraw drawings within the Obsidian application [4][5]. Its package.json file serves as a library/package manifest, but it is not the canonical source for the plugin's release version (which is managed in manifest.json and manifest-beta.json) [6][7]. The package.json defines dependencies, scripts for development and building, and library entry points [8]. These two projects serve different environments: the former for static site generation (Quartz) and the latter for the live Obsidian editing environment [1][4].
Citations:
- 1: https://github.com/quartz-community/obsidian-plugin-excalidraw
- 2: https://github.com/quartz-community/obsidian-plugin-excalidraw/blob/main/README.md
- 3: https://saberzero1.github.io/quartz-syncer-docs/settings/integrations/excalidraw
- 4: https://github.com/zsviczian/obsidian-excalidraw-plugin
- 5: https://community.obsidian.md/plugins/obsidian-excalidraw-plugin
- 6: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/f08e5777/AGENTS.md
- 7: zsviczian/obsidian-excalidraw-plugin@c341448
- 8: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/package.json
🌐 Web query:
Quartz CLI plugin add npm specifier github:obsidian-plugin-excalidraw
💡 Result:
To add the Quartz integration for the Obsidian Excalidraw plugin to your project, run the following command in your terminal within your Quartz project directory [1][2]: npx quartz plugin add github:quartz-community/obsidian-plugin-excalidraw After running this command, ensure the plugin is enabled in your quartz.config.yaml file [1][3]: plugins: - source: github:quartz-community/obsidian-plugin-excalidraw enabled: true options: enableInteraction: true darkMode: "auto" exportPadding: 20 This plugin allows your Quartz site to render Obsidian Excalidraw drawings (.excalidraw.md and.excalidraw files) as full-page, interactive SVG visualizations [1][4]. If you need to make advanced adjustments, you can also override settings in your quartz.ts file [1][3].
Citations:
- 1: https://github.com/quartz-community/obsidian-plugin-excalidraw
- 2: https://saberzero1.github.io/quartz-syncer-docs/settings/integrations/excalidraw
- 3: https://github.com/quartz-community/obsidian-plugin-excalidraw/blob/main/README.md
- 4: quartz-community/obsidian-plugin-excalidraw@2272491
Revert Excalidraw back to the GitHub source.
@quartz-community/obsidian-plugin-excalidraw is documented as installed via quartz plugin add github:quartz-community/obsidian-plugin-excalidraw, and this npm specifier can fail during template scaffold if it is not published. Keep the source: entry as a GitHub repo specifier, similar to the quartz-themes block above.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@quartz/cli/templates/obsidian.yaml` at line 271, Update the Excalidraw plugin
entry in the Obsidian template to use the GitHub repository specifier for
quartz-community/obsidian-plugin-excalidraw instead of the `@quartz-community` npm
package name. Preserve the existing source entry structure and align it with the
quartz-themes block and documented quartz plugin add command.
| const entryPoint = getPluginEntryPoint(spec.name) | ||
| module = await import(toFileUrl(entryPoint)) | ||
| } | ||
| const pluginName = spec.npmPackage ? spec.name : spec.name |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Redundant ternary — both branches return spec.name.
spec.npmPackage ? spec.name : spec.name always evaluates to spec.name. Simplify, or confirm the non-npm branch was intended to use a different value (e.g. a normalized/local name).
Proposed fix
- const pluginName = spec.npmPackage ? spec.name : spec.name
+ const pluginName = spec.name📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const pluginName = spec.npmPackage ? spec.name : spec.name | |
| const pluginName = spec.name |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@quartz/plugins/loader/config-loader.ts` at line 450, Update the plugin name
assignment in the config loader to use spec.name directly, removing the
redundant npmPackage ternary while preserving the resulting value.
| for (const npmPkg of options.npmPackages ?? []) { | ||
| let distIndex: string | undefined | ||
| try { | ||
| const esmRequire = createRequire(import.meta.url) | ||
| const pkgJsonPath = esmRequire.resolve(`${npmPkg}/package.json`) | ||
| distIndex = path.join(path.dirname(pkgJsonPath), "dist", "index.d.ts") | ||
| } catch { | ||
| if (options.verbose) { | ||
| console.log(styleText("yellow", `⚠`), `Skipping npm package ${npmPkg}: not found`) | ||
| } | ||
| continue | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Compare npm resolution options across call sites
rg -nP "resolve\(\`\\$\{.*\}/package\.json\`" quartzRepository: Faustze/notes
Length of output: 397
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== gitLoader relevant section =="
sed -n '980,1055p' quartz/plugins/loader/gitLoader.ts
echo
echo "== config-loader relevant section =="
sed -n '190,225p' quartz/plugins/loader/config-loader.ts
echo
echo "== package references =="
rg -n "npmPackages|options\\.verbose|Skipping npm package|npmPackages" quartz/plugins/loader/config-loader.ts quartz/plugins/loader/gitLoader.tsRepository: Faustze/notes
Length of output: 6337
Resolve npmPackages manifests from the project root.
gitLoader.ts resolves npmPackages with createRequire(import.meta.url), which can use Quartz’s module search path instead of the project root where users install npm plugin packages. Align this with config-loader.ts’s manifest resolution and pass { paths: [process.cwd()] } so installed packages are discovered consistently.
Proposed fix
const esmRequire = createRequire(import.meta.url)
- const pkgJsonPath = esmRequire.resolve(`${npmPkg}/package.json`)
+ const pkgJsonPath = esmRequire.resolve(`${npmPkg}/package.json`, { paths: [process.cwd()] })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const npmPkg of options.npmPackages ?? []) { | |
| let distIndex: string | undefined | |
| try { | |
| const esmRequire = createRequire(import.meta.url) | |
| const pkgJsonPath = esmRequire.resolve(`${npmPkg}/package.json`) | |
| distIndex = path.join(path.dirname(pkgJsonPath), "dist", "index.d.ts") | |
| } catch { | |
| if (options.verbose) { | |
| console.log(styleText("yellow", `⚠`), `Skipping npm package ${npmPkg}: not found`) | |
| } | |
| continue | |
| } | |
| for (const npmPkg of options.npmPackages ?? []) { | |
| let distIndex: string | undefined | |
| try { | |
| const esmRequire = createRequire(import.meta.url) | |
| const pkgJsonPath = esmRequire.resolve(`${npmPkg}/package.json`, { paths: [process.cwd()] }) | |
| distIndex = path.join(path.dirname(pkgJsonPath), "dist", "index.d.ts") | |
| } catch { | |
| if (options.verbose) { | |
| console.log(styleText("yellow", `⚠`), `Skipping npm package ${npmPkg}: not found`) | |
| } | |
| continue | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@quartz/plugins/loader/gitLoader.ts` around lines 1018 - 1029, Update the npm
package manifest resolution in the npmPackages loop to use createRequire with
the project root search path, passing { paths: [process.cwd()] } when resolving
`${npmPkg}/package.json`. Keep the existing dist/index.d.ts construction and
missing-package handling unchanged, matching the resolution approach used by
config-loader.ts.
Summary
Merges 23 commits from
upstream/v5(jackyzha0/quartz). We were 22-23 commits behind before this.Main upstream changes:
@quartz-community/*plugins, as an alternative togithub:git-source specifiersfix: decouple Head.tsx from og-image plugin import (#2495)Decision: migrated our config to npm specifiers
All 44 first-party plugins in
quartz.config.yaml/quartz.config.default.yamlwere switched fromgithub:quartz-community/*to@quartz-community/*npm specifiers. Our one local third-party plugin (./local-plugins/lang-switch) is untouched —github:sources remain fully supported.This removes the git-clone +
tsupbuild step for these plugins during CI. That's the same code path that caused this morning's flakyfavicon/og-imagebuild failure (sh: 1: tsup: not found) which I patched with a retry loop — npm packages now ship pre-built, so that whole class of flakiness goes away. Locally, the "Install Quartz plugins" step dropped from ~7 minutes to under 1 second.Conflict resolutions
package.json: kept our newer dependency versions (post dependabot merges), added upstream's ~44@quartz-community/*deps andhast-util-from-html(already imported directly indispatcher.tsbut missing from the manifest)package-lock.json: regenerated vianpm install, then re-applied theimmutable/brace-expansionsecurity bumps from the prior security PRquartz/plugins/loader/install-plugins.ts: took upstream's version — it handles both git- and npm-sourced plugins (strict superset of ours)quartz.lock.json: deleted (upstream removed it; no longer needed once plugins are npm-installed rather than git-cloned)Test plan
npx tsc --noEmit— cleannpx prettier . --check— clean (aside from pre-existing unrelatedcontent-ru/drift, not part of this branch)npx tsx --test— 109/109 passingnpx tsx ./quartz/plugins/loader/install-plugins.ts— all 36 plugins installed correctly, <1snpx quartz build— succeeds, 339 files emitted (same as before merge)npm ci(matching our deploy workflow) — succeeds with the regenerated lockfilenpm audit— 0 vulnerabilitieslang-switch(local plugin) still renders in output HTMLSummary by CodeRabbit
New Features
@quartz-community/....Documentation
Bug Fixes