From 007f367a3ea0521a83c4a8044dbb924ed64f1d9d Mon Sep 17 00:00:00 2001 From: christophe dervieux Date: Wed, 22 Apr 2026 15:20:12 +0200 Subject: [PATCH 1/3] Gate vendor.sh cache wipe behind QUARTO_SKIP_DENO_CACHE_WIPE Default behavior unchanged: local ./configure.sh still wipes deno_cache before re-vendoring. CI opts into cache preservation by setting QUARTO_SKIP_DENO_CACHE_WIPE=1, enabling actions/cache to restore the cache and have it survive vendor.sh. --- package/scripts/vendoring/vendor.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/scripts/vendoring/vendor.sh b/package/scripts/vendoring/vendor.sh index 04d899a51ed..976eb80d6b5 100755 --- a/package/scripts/vendoring/vendor.sh +++ b/package/scripts/vendoring/vendor.sh @@ -15,8 +15,9 @@ fi echo Revendoring quarto dependencies -# remove deno_cache directory first -if [ -d "$DENO_DIR" ]; then +# remove deno_cache directory first, unless explicitly told to preserve +# (CI sets QUARTO_SKIP_DENO_CACHE_WIPE=1 so a restored cache survives vendor.sh) +if [ -d "$DENO_DIR" ] && [ "${QUARTO_SKIP_DENO_CACHE_WIPE}" != "1" ]; then rm -rf "$DENO_DIR" fi From 68036d325e1662e4c0594f8978515912d8d36a2a Mon Sep 17 00:00:00 2001 From: christophe dervieux Date: Wed, 22 Apr 2026 15:26:22 +0200 Subject: [PATCH 2/3] Gate vendor.cmd cache wipe behind QUARTO_SKIP_DENO_CACHE_WIPE Windows counterpart to the vendor.sh change. Behavior matches: default wipes, QUARTO_SKIP_DENO_CACHE_WIPE=1 preserves. --- package/scripts/vendoring/vendor.cmd | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/scripts/vendoring/vendor.cmd b/package/scripts/vendoring/vendor.cmd index f9ab84973fa..e43fae3577b 100644 --- a/package/scripts/vendoring/vendor.cmd +++ b/package/scripts/vendoring/vendor.cmd @@ -18,9 +18,12 @@ IF NOT DEFINED DENO_DIR ( ECHO Revendoring quarto dependencies -REM remove deno_cache directory first +REM remove deno_cache directory first, unless explicitly told to preserve +REM (CI sets QUARTO_SKIP_DENO_CACHE_WIPE=1 so a restored cache survives vendor.cmd) IF EXIST "!DENO_DIR!" ( - RMDIR /S /Q "!DENO_DIR!" + IF NOT "!QUARTO_SKIP_DENO_CACHE_WIPE!"=="1" ( + RMDIR /S /Q "!DENO_DIR!" + ) ) PUSHD "!QUARTO_SRC_PATH!" From 3cbde75ad0867c204bc3eb8137241824ba608b96 Mon Sep 17 00:00:00 2001 From: christophe dervieux Date: Wed, 22 Apr 2026 15:48:16 +0200 Subject: [PATCH 3/3] Cache Deno development cache in CI Adds actions/cache restore for ./package/dist/bin/deno_cache, keyed on the five files that influence Deno dep resolution: configuration, src/import_map.json, src/vendor_deps.ts, tests/test-deps.ts, and package/scripts/deno_std/deno_std.ts. Both configure steps set QUARTO_SKIP_DENO_CACHE_WIPE=1 so the restored cache survives the configure.sh / configure.cmd run. Adds dev-docs/ci-deno-caching.md explaining the cache layout, key composition, env var contract, and how to force invalidation. Adds a cross-link from dev-docs/upgrade-dependencies.md so a contributor bumping deno_std version sees the cache consequences. Expected per-OS cache size ~150-200 MB; total footprint ~600 MB across Linux/macOS/Windows, well under GitHub's 10 GB repo quota. --- .../workflows/actions/quarto-dev/action.yml | 12 ++ dev-docs/ci-deno-caching.md | 107 ++++++++++++++++++ dev-docs/upgrade-dependencies.md | 2 + 3 files changed, 121 insertions(+) create mode 100644 dev-docs/ci-deno-caching.md diff --git a/.github/workflows/actions/quarto-dev/action.yml b/.github/workflows/actions/quarto-dev/action.yml index 30936f8f54f..d1906ab23c3 100644 --- a/.github/workflows/actions/quarto-dev/action.yml +++ b/.github/workflows/actions/quarto-dev/action.yml @@ -12,9 +12,19 @@ runs: restore-keys: | ${{ runner.os }}-deno_std-2- + - name: Cache Deno development cache + uses: actions/cache@v5 + with: + path: ./package/dist/bin/deno_cache + key: ${{ runner.os }}-${{ runner.arch }}-deno-cache-v1-${{ hashFiles('configuration', 'src/import_map.json', 'src/vendor_deps.ts', 'tests/test-deps.ts', 'package/scripts/deno_std/deno_std.ts') }} + restore-keys: | + ${{ runner.os }}-${{ runner.arch }}-deno-cache-v1- + - name: Configure Quarto (.sh) if: runner.os != 'Windows' shell: bash + env: + QUARTO_SKIP_DENO_CACHE_WIPE: "1" run: | # install with symlink in /usr/local/bin which in PATH on CI ./configure.sh @@ -22,6 +32,8 @@ runs: - name: Configure Quarto (.ps1) if: runner.os == 'Windows' shell: pwsh + env: + QUARTO_SKIP_DENO_CACHE_WIPE: "1" run: | ./configure.cmd "$(Get-ChildItem -Path ./package/dist/bin/quarto.cmd | %{ $_.FullName } | Split-Path)" >> $env:GITHUB_PATH diff --git a/dev-docs/ci-deno-caching.md b/dev-docs/ci-deno-caching.md new file mode 100644 index 00000000000..b368d127745 --- /dev/null +++ b/dev-docs/ci-deno-caching.md @@ -0,0 +1,107 @@ +# CI Deno cache + +How the Quarto development Deno cache is cached in GitHub Actions, how +invalidation works, and how to force a fresh download. + +## What gets cached + +Two Deno caches are restored by the `quarto-dev` composite action +(`.github/workflows/actions/quarto-dev/action.yml`): + +| Cache | Path | Populated by | +|-------|------|--------------| +| Deno standard library | `./src/resources/deno_std/cache` | `package/scripts/deno_std/deno_std.ts` | +| Deno development cache | `./package/dist/bin/deno_cache` | `package/scripts/vendoring/vendor.sh` / `vendor.cmd` | + +This document covers the second one. The first predates it and is keyed on +`deno_std.lock` + `deno_std.ts`. + +## Cache key + +``` +${{ runner.os }}-${{ runner.arch }}-deno-cache-v1- +``` + +`` is `hashFiles()` over five files: + +| File | Why it's in the key | +|------|---------------------| +| `configuration` | Pins the Deno binary version. | +| `src/import_map.json` | Top-level dep version map — the primary driver of what gets downloaded. | +| `src/vendor_deps.ts` | Explicit "things to vendor" entrypoint. | +| `tests/test-deps.ts` | Test-only deps entrypoint (iterated by vendor.sh / vendor.cmd). | +| `package/scripts/deno_std/deno_std.ts` | deno_std entrypoint iterated by the vendor scripts. | + +`restore-keys` falls back to the same prefix without the hash suffix, so a +partial-match cache from a sibling branch is reused if the exact key misses. + +### Not in the key + +- `quarto.ts` and other `src/**/*.ts` files: these are consumer code, not + dep-resolution inputs. `deno install` is additive on top of an existing + cache, so new imports in consumer code download without needing a key change. + Hashing `src/**` would invalidate on every commit. +- `vendor.sh` / `vendor.cmd`: they control *how* `deno install` runs, not + *what* it resolves. Including them would cross-invalidate (a Unix-only + `vendor.sh` edit would bust the Windows cache too, since `hashFiles()` is + OS-independent). +- `deno.lock` / `tests/deno.lock`: both are gitignored (`.gitignore:26`) and + are generated by `deno install` at run time, so they do not exist on a + fresh checkout and cannot serve as key inputs. + +## Wipe gate + +`vendor.sh` and `vendor.cmd` delete `./package/dist/bin/deno_cache` before +running `deno install`. That default is preserved for local development. In +CI we need the opposite — the cache was just restored by `actions/cache` and +must survive until `deno install` uses it. + +The coordination uses a single environment variable: + +- **Name:** `QUARTO_SKIP_DENO_CACHE_WIPE` +- **Truthy value:** `"1"` (anything else, including unset, means "wipe"). +- **Set by:** both configure steps in `action.yml` (`env: QUARTO_SKIP_DENO_CACHE_WIPE: "1"`). +- **Read by:** `vendor.sh` and `vendor.cmd`. Nothing else. +- **Do not set locally.** The variable only makes sense when paired with a + restore step. Without one, skipping the wipe just leaves a stale cache on + disk. + +## Forcing invalidation + +### When it happens automatically + +Editing any of the five keyed files changes the hash, which produces a new +cache key. Next CI run misses, re-downloads, then saves under the new key. +Old keys age out via GitHub's 7-day LRU. + +### When you have to force it manually + +Only if the cache contents go bad in a way the key can't detect — e.g. a +partial download that wasn't corrected, or a silent shape change from a new +Deno version that still resolves to the same `configuration` string. In that +case, bump the `-v1-` version prefix in both the `key` and `restore-keys` of +the cache step in `action.yml`: + +```diff +- key: ${{ runner.os }}-${{ runner.arch }}-deno-cache-v1-... ++ key: ${{ runner.os }}-${{ runner.arch }}-deno-cache-v2-... + restore-keys: | +- ${{ runner.os }}-${{ runner.arch }}-deno-cache-v1- ++ ${{ runner.os }}-${{ runner.arch }}-deno-cache-v2- +``` + +This avoids editing any of the five hashed files (which may not even be +involved in the regression) and leaves a clear audit trail in git history. + +## Rollback + +- **Cache misbehaves:** delete the "Cache Deno development cache" step in + `action.yml`. `configure.sh` / `configure.cmd` run `vendor.sh` / + `vendor.cmd` as before and redownload from scratch. +- **Wipe gate misbehaves:** remove the `QUARTO_SKIP_DENO_CACHE_WIPE` env + from the configure steps. Vendor scripts fall back to the default wipe. + +## Expected footprint + +~150–200 MB per OS, three OSes → ~600 MB total repo cache usage, well under +GitHub's 10 GB per-repo quota. diff --git a/dev-docs/upgrade-dependencies.md b/dev-docs/upgrade-dependencies.md index fe52814715d..6c888c15c87 100644 --- a/dev-docs/upgrade-dependencies.md +++ b/dev-docs/upgrade-dependencies.md @@ -12,6 +12,8 @@ Contact Carlos so he uploads the binaries to the S3 bucket. - run `./configure.sh`. +Bumping a version in `src/import_map.json` (or any of the other keyed files) automatically invalidates the CI Deno cache on next run. See [ci-deno-caching.md](ci-deno-caching.md) for the key composition and how to force invalidation manually. + ### Upgrade Deno download link for RHEL build from conda-forge - Go to and find the version of Deno required.