Conversation
- Add config option ("all" | "referenced") to GlobalConfiguration
- Rewrite Assets emitter to extract asset refs from filtered content HAST
- Only copy intersected files when publishAssets: "referenced"
- Add integration test with fixture covering ExplicitPublish/RemoveDrafts
Fixes jackyzha0#2531
There was a problem hiding this comment.
🟡 Changes recommended
The referenced-asset extraction and incremental emit logic have correctness gaps, and the new integration test is currently non-portable/broken due to incorrect paths and a hard-coded Node binary.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an opt-in publishAssets: "referenced" mode so the Assets emitter copies only assets referenced by published pages (to avoid leaking attachments from filtered/unpublished notes), and introduces a fixture-based integration test for the behavior.
Changes:
- Added
configuration.publishAssets?: "all" | "referenced"(defaulting to"all"for backwards compatibility). - Reworked
Assetsemitter to scan processed HAST trees for asset references and conditionally copy only those assets. - Added an integration test + fixture vault to verify that assets referenced only by private/draft pages (or orphaned) are not emitted.
File summaries
| File | Description |
|---|---|
| quartz/cfg.ts | Adds the new publishAssets global config option. |
| quartz/plugins/emitters/assets.ts | Implements referenced-only asset discovery and conditional copying. |
| quartz/plugins/emitters/assets.test.ts | Adds an integration test that runs a build and asserts emitted assets. |
| test/fixtures/asset-filtering/quartz.config.yaml | Fixture config enabling filter plugins + publishAssets: "referenced". |
| test/fixtures/asset-filtering/content/published.md | Published page that references two assets. |
| test/fixtures/asset-filtering/content/private.md | Unpublished page that references a “secret” asset. |
| test/fixtures/asset-filtering/content/draft.md | Draft page that references a draft-only asset. |
| test/fixtures/asset-filtering/content/diagram.png | Asset referenced by published content (should be copied). |
| test/fixtures/asset-filtering/content/assets/nested.png | Nested asset referenced by published content (should be copied). |
| test/fixtures/asset-filtering/content/secret.png | Asset referenced only by private content (should not be copied). |
| test/fixtures/asset-filtering/content/draft-img.png | Asset referenced only by draft content (should not be copied). |
| test/fixtures/asset-filtering/content/orphan.png | Unreferenced asset (should not be copied). |
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 7
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const PROJECT_ROOT = path.join(__dirname, "..", "..", "..") | ||
| const TEST_FIXTURE = path.join(__dirname, "fixtures", "asset-filtering") | ||
| const OUTPUT_DIR = path.join(PROJECT_ROOT, "test/fixtures/asset-filtering/public") | ||
| const TEST_CONFIG = path.join(PROJECT_ROOT, "quartz.config.test.yaml") | ||
|
|
|
|
||
| function runQuartzBuild(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> { | ||
| return new Promise((resolve) => { | ||
| const child = spawn("/opt/homebrew/bin/node", [ |
| /** | ||
| * Controls which assets are copied to public/ | ||
| * - "all": Copy all non-markdown files (backwards compatible, default) | ||
| * - "referenced": Only copy assets referenced by published pages | ||
| */ | ||
| publishAssets?: "all" | "referenced" |
| describe("Assets emitter with publishAssets config", () => { | ||
| before(async () => { | ||
| await cleanOutput() | ||
| // Use test config for this test suite | ||
| const testConfigContent = await fs.readFile(TEST_CONFIG, "utf-8") | ||
| await fs.writeFile(path.join(PROJECT_ROOT, "quartz.config.yaml"), testConfigContent) | ||
| }) | ||
|
|
||
| after(async () => { | ||
| await cleanOutput() | ||
| // Restore original config (quartz.config.default.yaml) | ||
| const defaultConfigContent = await fs.readFile(path.join(PROJECT_ROOT, "quartz.config.default.yaml"), "utf-8") | ||
| await fs.writeFile(path.join(PROJECT_ROOT, "quartz.config.yaml"), defaultConfigContent) | ||
| }) |
| function normalizeAssetPath(raw: string): string { | ||
| return path.posix.normalize(raw.split("?")[0].split("#")[0]).replace(/^\.\//, "") | ||
| } |
| visit(tree, "element", (node: Element) => { | ||
| const src = node.properties?.src | ||
| if (!src || typeof src !== "string") return | ||
| if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("data:")) return | ||
|
|
||
| let normalized = src.split("?")[0].split("#")[0] | ||
| if (baseDir && !normalized.startsWith("/")) { | ||
| normalized = path.posix.join(baseDir, normalized) | ||
| } | ||
| normalized = path.posix.normalize(normalized).replace(/^\.\//, "") | ||
| assets.add(normalized) | ||
| }) |
| const referencedAssets = publishAssets === "referenced" ? extractAssetReferences(content) : null | ||
|
|
||
| for (const changeEvent of changeEvents) { |
- Fix test paths and use process.execPath for portability - Add publishAssets to JSON schema for validation - Handle root-relative asset paths (strip leading /) - Track href links in addition to src attributes - Fix partialEmit to handle markdown changes (re-extract references) - Make test hermetic with proper config save/restore - Expand test fixture config with full plugin set for correct filter behavior Fixes jackyzha0#2531
built with Refined Cloudflare Pages Action⚡ Cloudflare Pages Deployment
|
- Fix Windows test failure: fs.readdir(recursive) returns backslash paths on Windows, breaking forward-slash assertions. Normalize separators before comparing. - Simplify test hermeticity (single before/after cleanup, no more fragile double-branch config verification/restoration logic). - Fix dead-code branch in partialEmit: when a markdown file's publish status changes without the asset file itself changing, reconcile the full referenced-asset set instead of only looking at changeEvents so watch mode correctly adds/removes affected assets. - Remove files that were unintentionally included in this PR and are unrelated to asset filtering: quartz.config.yaml (previously removed from the repo on purpose), quartz.config.test.yaml, the two docs/architecture/*.md files, and a stray .omo session artifact (now gitignored).
|
Pushed a follow-up commit addressing the remaining issues from review and CI: Windows CI fix (root cause of the failing Test hermeticity: simplified the config swap/restore in
Removed unrelated files that had snuck into the diff (visible via
All 164 tests pass locally, and I verified the config-swap test cleans up correctly (leaves no |
Summary
Fixes #2531 — Assets from unpublished notes were being emitted when
explicit-publish(and other filter plugins) are enabled.Changes
1. New Config Option (
quartz/cfg.ts)Added
publishAssets?: "all" | "referenced"toGlobalConfiguration:"all"(default) — copies all non-markdown files (backwards compatible, preserves existing behavior)"referenced"— only copies assets referenced by published pages2. Assets Emitter Rewrite (
quartz/plugins/emitters/assets.ts)Modified
emit()andpartialEmit()to:srcreferences (img,video,audio,iframe,source,track)3. Test Coverage (
quartz/plugins/emitters/assets.test.ts+ fixture)Added integration test with fixture that verifies:
Usage
In
quartz.config.yaml:Backwards Compatibility
"all"preserves existing behavior — existing sites unaffected"referenced"enables the new filteringTesting
All 164 existing tests pass + new integration test.