From dfd43d944700fda97b9fb2ed6227cf0aaad88c49 Mon Sep 17 00:00:00 2001 From: MichaelHatherly Date: Thu, 9 Apr 2026 12:33:01 +0100 Subject: [PATCH 1/2] Squashed 'src/resources/extension-subtrees/julia-engine/' changes from f591fc4..cb74379 --- .../extension-subtrees/julia-engine/AGENTS.md | 4 +++ .../_extensions/julia-engine/Project.toml | 2 +- .../_extensions/julia-engine/_extension.yml | 2 +- .../_extensions/julia-engine/julia-engine.js | 6 ++++ .../julia-engine/src/constants.ts | 1 + .../julia-engine/src/julia-engine.ts | 9 +++++ .../tests/docs/julia-engine/.gitignore | 1 + .../engine-reordering/_metadata.yml | 1 + .../engine-reordering/_quarto.yml | 3 -- .../tests/smoke/julia-engine/render.test.ts | 34 +++++++++++++++++++ .../julia-engine/keep-ipynb/keep-ipynb.qmd | 10 ++++++ .../julia-engine/keep-ipynb/no-keep-ipynb.qmd | 9 +++++ 12 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_metadata.yml delete mode 100644 src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_quarto.yml create mode 100644 tests/docs/julia-engine/keep-ipynb/keep-ipynb.qmd create mode 100644 tests/docs/julia-engine/keep-ipynb/no-keep-ipynb.qmd diff --git a/src/resources/extension-subtrees/julia-engine/AGENTS.md b/src/resources/extension-subtrees/julia-engine/AGENTS.md index 4c7e6d70102..4329f3e456f 100644 --- a/src/resources/extension-subtrees/julia-engine/AGENTS.md +++ b/src/resources/extension-subtrees/julia-engine/AGENTS.md @@ -59,3 +59,7 @@ CI runs on all three platforms (Linux, macOS, Windows) against a pinned quarto-c 3. Runs the full test suite When bumping `QUARTO_CLI_REV`, use the full commit hash annotated with the version tag for clarity (e.g. `abc123 # v1.9.35`). + +## Changelog + +Every PR with user-facing or otherwise meaningful changes must include an update to `CHANGELOG.md` (enforced by CI). Add entries under the `## Unreleased` section. Use the `skip-changelog` label to bypass the check for PRs that don't need an entry (e.g. internal cleanups, CI, or docs changes). diff --git a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/Project.toml b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/Project.toml index 94def5bfc2f..a479d9bac73 100644 --- a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/Project.toml +++ b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/Project.toml @@ -2,4 +2,4 @@ QuartoNotebookRunner = "4c0109c6-14e9-4c88-93f0-2b974d3468f4" [compat] -QuartoNotebookRunner = "=0.17.4" +QuartoNotebookRunner = "=0.18.1" diff --git a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/_extension.yml b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/_extension.yml index d97f9d81777..5dbee8af028 100644 --- a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/_extension.yml +++ b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/_extension.yml @@ -1,5 +1,5 @@ title: Quarto Julia Engine Extension -version: 0.1.0 +version: 0.2.0 quarto-required: ">=1.9.0" contributes: engines: diff --git a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/julia-engine.js b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/julia-engine.js index d3a33c0f9ac..a31aa5a88f8 100644 --- a/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/julia-engine.js +++ b/src/resources/extension-subtrees/julia-engine/_extensions/julia-engine/julia-engine.js @@ -631,6 +631,7 @@ var kFigFormat = "fig-format"; var kFigPos = "fig-pos"; var kIpynbProduceSourceNotebook = "produce-source-notebook"; var kKeepHidden = "keep-hidden"; +var kKeepIpynb = "keep-ipynb"; // src/julia-engine.ts var isWindows2 = Deno.build.os === "windows"; @@ -749,6 +750,11 @@ var juliaEngineDiscovery = { language: "julia" }; const assets = quarto.jupyter.assets(options.target.input, options.format.pandoc.to); + if (options.format.execute[kKeepIpynb]) { + const stem = options.target.source.replace(/\.[^.]+$/, ""); + const ipynbPath = stem + ".ipynb"; + Deno.writeTextFileSync(ipynbPath, JSON.stringify(nb, null, 2)); + } const result = await quarto.jupyter.toMarkdown(nb, { executeOptions: options, language: nb.metadata.kernelspec.language.toLowerCase(), diff --git a/src/resources/extension-subtrees/julia-engine/src/constants.ts b/src/resources/extension-subtrees/julia-engine/src/constants.ts index 1664bf2c2fe..fe57bb66537 100644 --- a/src/resources/extension-subtrees/julia-engine/src/constants.ts +++ b/src/resources/extension-subtrees/julia-engine/src/constants.ts @@ -13,3 +13,4 @@ export const kFigFormat = "fig-format"; export const kFigPos = "fig-pos"; export const kIpynbProduceSourceNotebook = "produce-source-notebook"; export const kKeepHidden = "keep-hidden"; +export const kKeepIpynb = "keep-ipynb"; diff --git a/src/resources/extension-subtrees/julia-engine/src/julia-engine.ts b/src/resources/extension-subtrees/julia-engine/src/julia-engine.ts index 233ca1dd861..5fdc5adb19c 100644 --- a/src/resources/extension-subtrees/julia-engine/src/julia-engine.ts +++ b/src/resources/extension-subtrees/julia-engine/src/julia-engine.ts @@ -37,6 +37,7 @@ import { kIpynbProduceSourceNotebook, kJuliaEngine, kKeepHidden, + kKeepIpynb, } from "./constants.ts"; // Platform detection @@ -220,6 +221,14 @@ export const juliaEngineDiscovery: ExecutionEngineDiscovery = { options.format.pandoc.to, ); + // Write notebook to file if keep-ipynb is set (must happen before + // toMarkdown which mutates nb in place) + if (options.format.execute[kKeepIpynb]) { + const stem = options.target.source.replace(/\.[^.]+$/, ""); + const ipynbPath = stem + ".ipynb"; + Deno.writeTextFileSync(ipynbPath, JSON.stringify(nb, null, 2)); + } + // NOTE: for perforance reasons the 'nb' is mutated in place // by jupyterToMarkdown (we don't want to make a copy of a // potentially very large notebook) so should not be relied diff --git a/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/.gitignore b/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/.gitignore index 351aa45ca55..a81c62f0e5e 100644 --- a/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/.gitignore +++ b/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/.gitignore @@ -4,3 +4,4 @@ # Allow only source files !*/*.qmd !*/_quarto.yml +!*/_metadata.yml diff --git a/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_metadata.yml b/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_metadata.yml new file mode 100644 index 00000000000..2d9223fda7f --- /dev/null +++ b/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_metadata.yml @@ -0,0 +1 @@ +engines: ["julia"] diff --git a/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_quarto.yml b/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_quarto.yml deleted file mode 100644 index 3aaa4272ac6..00000000000 --- a/src/resources/extension-subtrees/julia-engine/tests/docs/julia-engine/engine-reordering/_quarto.yml +++ /dev/null @@ -1,3 +0,0 @@ -project: - type: default -engines: ["julia"] diff --git a/src/resources/extension-subtrees/julia-engine/tests/smoke/julia-engine/render.test.ts b/src/resources/extension-subtrees/julia-engine/tests/smoke/julia-engine/render.test.ts index 91e66751d41..c79e308baac 100644 --- a/src/resources/extension-subtrees/julia-engine/tests/smoke/julia-engine/render.test.ts +++ b/src/resources/extension-subtrees/julia-engine/tests/smoke/julia-engine/render.test.ts @@ -44,6 +44,40 @@ Deno.test("source ranges with includes", async () => { try { Deno.removeSync(outputFile); } catch { /* ok */ } }); +Deno.test("keep-ipynb", async () => { + const dir = docs("julia-engine/keep-ipynb"); + const input = join(dir, "keep-ipynb.qmd"); + renderQmd(input, ["--to", "html"]); + + const outputHtml = join(dir, "keep-ipynb.html"); + assert(existsSync(outputHtml), `Output file ${outputHtml} should exist`); + + const ipynbFile = join(dir, "keep-ipynb.ipynb"); + assert(existsSync(ipynbFile), `keep-ipynb file ${ipynbFile} should exist`); + + const content = await Deno.readTextFile(ipynbFile); + const nb = JSON.parse(content); + assert(nb.cells, "Notebook should have cells"); + assert(nb.metadata, "Notebook should have metadata"); + + try { Deno.removeSync(outputHtml); } catch { /* ok */ } + try { Deno.removeSync(ipynbFile); } catch { /* ok */ } +}); + +Deno.test("no keep-ipynb by default", () => { + const dir = docs("julia-engine/keep-ipynb"); + const input = join(dir, "no-keep-ipynb.qmd"); + renderQmd(input, ["--to", "html"]); + + const outputHtml = join(dir, "no-keep-ipynb.html"); + assert(existsSync(outputHtml), `Output file ${outputHtml} should exist`); + + const ipynbFile = join(dir, "no-keep-ipynb.ipynb"); + assert(!existsSync(ipynbFile), `ipynb file ${ipynbFile} should NOT exist without keep-ipynb`); + + try { Deno.removeSync(outputHtml); } catch { /* ok */ } +}); + Deno.test("engine reordering", () => { const dir = docs("julia-engine/engine-reordering"); renderQmd("notebook.qmd", ["--to", "html"], dir); diff --git a/tests/docs/julia-engine/keep-ipynb/keep-ipynb.qmd b/tests/docs/julia-engine/keep-ipynb/keep-ipynb.qmd new file mode 100644 index 00000000000..7a8629f363f --- /dev/null +++ b/tests/docs/julia-engine/keep-ipynb/keep-ipynb.qmd @@ -0,0 +1,10 @@ +--- +title: keep-ipynb test +engine: julia +format: html +keep-ipynb: true +--- + +```{julia} +1 + 1 +``` diff --git a/tests/docs/julia-engine/keep-ipynb/no-keep-ipynb.qmd b/tests/docs/julia-engine/keep-ipynb/no-keep-ipynb.qmd new file mode 100644 index 00000000000..fb2b02f6e92 --- /dev/null +++ b/tests/docs/julia-engine/keep-ipynb/no-keep-ipynb.qmd @@ -0,0 +1,9 @@ +--- +title: no keep-ipynb test +engine: julia +format: html +--- + +```{julia} +1 + 1 +``` From 5b05601611ceb5e4b5476eb5b16926208f6389e6 Mon Sep 17 00:00:00 2001 From: MichaelHatherly Date: Thu, 9 Apr 2026 12:33:07 +0100 Subject: [PATCH 2/2] changelog: julia engine 0.2.0 / QNR 0.18.1 --- news/changelog-1.10.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index 2306b5ec79a..ba6ee960039 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -7,6 +7,20 @@ All changes included in 1.10: - ([#14298](https://github.com/quarto-dev/quarto-cli/issues/14298)): Fix `quarto preview` browse URL including output filename (e.g., `hello.html`) for single-file documents, breaking Posit Workbench proxied server access. - ([rstudio/rstudio#17333](https://github.com/rstudio/rstudio/issues/17333)): Fix `quarto inspect` on standalone files emitting project metadata that breaks RStudio's publishing wizard. +## Engines + +### `julia` + +- Update `julia` engine extension from 0.1.0 to 0.2.0 and QuartoNotebookRunner from 0.17.4 to 0.18.1. +- Add `keep-ipynb` support. When `keep-ipynb: true` is set in document YAML, the executed notebook is written alongside the source file. +- Add shared worker processes. Multiple notebooks sharing the same config can reuse a single worker process via `julia: share_worker_process: true`. +- Add support for `execute-dir: project` to set working directory to project root. +- Add diagnostic file logging via `QUARTONOTEBOOKRUNNER_LOG` environment variable. +- Cache worker package environments across sessions, skipping environment setup on subsequent runs when Julia and QNR versions haven't changed. +- Relax manifest version checking to major.minor by default, matching Julia's Pkg behavior. Opt into strict checking via `julia: strict_manifest_versions: true`. +- Fix `fig-format: retina` by normalizing to `png` with doubled `fig-dpi`, matching Jupyter and knitr backends. +- Fix cache invalidation to hash full `Manifest.toml` content so dependency version changes correctly invalidate the cache. +- Fix duplicate YAML keys when Python/R cells have cell options like `echo`. ## Formats ### `typst`