Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/actions/quarto-dev/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,28 @@ 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

- 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
Expand Down
107 changes: 107 additions & 0 deletions dev-docs/ci-deno-caching.md
Original file line number Diff line number Diff line change
@@ -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-<hash>
```

`<hash>` 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.
2 changes: 2 additions & 0 deletions dev-docs/upgrade-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://anaconda.org/conda-forge/deno/files> and find the version of Deno required.
Expand Down
7 changes: 5 additions & 2 deletions package/scripts/vendoring/vendor.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
Expand Down
5 changes: 3 additions & 2 deletions package/scripts/vendoring/vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading