Fix keep-md cleanup deleting the markdown output when output-file has an .html extension - #14671
Conversation
When output-file has an .html extension and an html format is paired with a markdown format (e.g. output-file: index.html with html + commonmark, as nbdev sets up for llms.txt workflows), the markdown output is named index.html.md -- the same path the keep-md intermediate convention (<input-stem>.<base-format>.md) assigns to the html format. The html render's cleanup removed the "stale intermediate", deleting the freshly written markdown output: website renders then failed at the output-move step, and other project types silently lost the file. Compute the projected output paths of the formats being rendered and attach them to each RenderContext; render cleanup now never deletes a path that a format owns, and a keep-md: true render whose intermediate location collides warns and skips saving the intermediate instead of overwriting the output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…14669) A partial render (quarto render --to html) of a document whose other declared format owns the keep-md-conventional path (output-file: index.html + a markdown format -> index.html.md) was still deleting the markdown output a previous full render produced, since outputs live next to sources in default-type projects. renderContexts already resolves every declared format (inactive ones are simply marked active: false), so extending the protection is just including inactive contexts when collecting the owned output paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
(Claude says:) Note on the 8 failing windows checks (analysis after re-running them — they fail identically across 3 attempts):
I couldn't bisect the specific polluting test without a windows box; happy to file the CI investigation as a separate issue if this diagnosis looks right to maintainers. |
The collision guard was inlined in both renderCleanup and the keep-md write path; keepMdCollidesWithFormatOutput keeps the invariant (never write to or clean up a path another format owns) in one place. Trim the changelog entry to the user-facing symptom -- the keep-md/output-move mechanism and the nbdev/commonmark example already live in the PR body.
|
LGTM. Traced all four paths (default / website / Two changes on top of the fix:
One thing I noticed while in here: book and manuscript renders call |
Closes #14669.
Problem
Pairing an html format with a markdown format while
output-filehas an.htmlextension (e.g.output-file: index.htmlwithformat: html+commonmark, as nbdev sets up for llms.txt workflows) names the markdown outputindex.html.md— exactly the path the keep-md intermediate convention (<input-stem>.<base-format>.md,executionEngineKeepMd) assigns to the html format. The html render's cleanup removed what it took for a stale intermediate, deleting the freshly written markdown output:NotFound … rename '<proj>/index.html.md' -> '<proj>/_site/index.html.md'), leavingindex.htmlandsite_libs/stranded in the project rootkeep-md: true: no crash, but the html render's intermediate silently fought with the markdown output for the same fileDiagnosis was confirmed by instrumenting
renderCleanupand watching the filesystem at 2ms resolution: the commonmark output is written and deleted 29ms later by the html format's cleanup.Fix
Principle: keep-md intermediate handling must never write to or delete a path that a declared format owns.
Commit 1 (core fix): compute the projected output paths of the formats being rendered (
projectedOutputFile, next toformatOutputFileincore/render.ts), attach them to eachRenderContext, andrenderCleanupwhen the conventional keep-md path is another format's outputCommit 2 (partial-render protection, split out for review): a partial render (
quarto render --to html) must not delete the markdown twin a previous full render produced (outputs live in place in default-type projects). SincerenderContextsalready resolves every declared format (inactive ones are markedactive: false), the implementation is just dropping the active-only filter from commit 1's computation — one line plus a test.Tests
New smoke tests in
tests/smoke/render/render-output-file-collision.test.ts(fixtures undertests/docs/output-file-collision/):_site, no root debris--to htmlpartial render preserves an existing twin (commit 2)keep-md: truewarns, and the twin holds the commonmark output rather than the intermediateEach commit's tests verified to fail without that commit's change. Genuine stale-intermediate cleanup still works (
keep-md: truerender followed bykeep-md: falseremoves the leftover). Together with #14670, the full nbdev/llms.txt scenario from #14667 now works end-to-end (verified on a local merge of both branches: render succeeds and the served page'sindex.html.mdlink resolves with HTTP 200).🤖 Generated with Claude Code