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: 11 additions & 1 deletion .github/actions/typst-cache/action.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
name: Restore Typst WASM cache
description: Reuse compiled Typst WASM after dependency installation
outputs:
hash:
description: Content hash of the Typst WASM build inputs
value: ${{ steps.fingerprint.outputs.hash }}
runs:
using: composite
steps:
- name: Fingerprint Typst WASM inputs
id: fingerprint
shell: bash
run: |
hash=$(node packages/xl-typst-compiler/scripts/wasm-cache.mjs)
echo "hash=$hash" >> "$GITHUB_OUTPUT"
# Cache just the distributable (~25 MB), not the multi-GB Rust target tree.
# ensure-wasm.mjs still verifies its content hash before using this output.
- name: Cache Typst WASM
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: packages/xl-typst-compiler/pkg
key: typst-wasm-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('packages/xl-typst-compiler/rust/Cargo.toml', 'packages/xl-typst-compiler/rust/Cargo.lock', 'packages/xl-typst-compiler/rust/rust-toolchain.toml', 'packages/xl-typst-compiler/rust/src/**', 'packages/xl-typst-compiler/scripts/ensure-wasm.mjs', 'packages/xl-typst-compiler/package.json', 'packages/xl-typst-compiler/node_modules/wasm-pack/package.json') }}
key: typst-wasm-v3-${{ steps.fingerprint.outputs.hash }}
18 changes: 18 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,30 @@ jobs:
cache: true

- uses: $/.github/actions/typst-cache
id: typst-cache

# Compiles the Rust wasm when missing/stale (rustup is bootstrapped by
# the build task itself) plus the TS wrapper's dist/types.
- name: Build Typst compiler package
run: vp run --filter @blocknote/xl-typst-compiler build

# Developer downloads accept only successful main push runs. Keep this
# separate from the full package artifact consumed by the PDF jobs.
- name: Upload content-addressed WASM
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: typst-wasm-v3-${{ steps.typst-cache.outputs.hash }}
path: |
packages/xl-typst-compiler/pkg/blocknote_typst_wasm.js
packages/xl-typst-compiler/pkg/blocknote_typst_wasm.d.ts
packages/xl-typst-compiler/pkg/blocknote_typst_wasm_bg.wasm
packages/xl-typst-compiler/pkg/blocknote_typst_wasm_bg.wasm.d.ts
packages/xl-typst-compiler/pkg/.build-manifest.json
include-hidden-files: true
if-no-files-found: error
retention-days: 90

- name: Upload compiler build outputs
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ jobs:
# rustup auto-provisions the pinned toolchain from
# rust/rust-toolchain.toml.
- name: Build packages
run: vp run -r build
run: vp run --no-cache -r build
env:
# Release provenance must come from this source checkout, not reused
# developer/CI WASM outputs or Vite task-cache replay.
BLOCKNOTE_TYPST_SOURCE_ONLY: "1"

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
Expand Down
63 changes: 57 additions & 6 deletions packages/xl-typst-compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,63 @@ The wasm module (`pkg/`) is built from `rust/` (toolchain pinned in
pnpm exec vp run --filter @blocknote/xl-typst-compiler build
```

The build task self-provisions: `scripts/ensure-wasm.mjs` compiles `pkg/`
from `rust/` when it is missing or stale (content-hashed), before the
TypeScript build. It needs rustup locally (the pinned toolchain and wasm32
target auto-install); on Vercel it bootstraps rustup itself and keeps the
cargo caches under `node_modules/.cache`, so only the first build on a
fresh cache pays the full compile. `npm run build:wasm` forces a rebuild.
The build task checks the Rust sources, pinned toolchain, resolved wasm-pack
version, and the output-producing `scripts/wasm-build.mjs` contract using one
content hash shared with CI. That contract owns the wasm-pack invocation and
output file list and is hashed automatically; cache/download orchestration and
logging changes do not invalidate otherwise identical WASM. Keep changes to
compilation settings in that contract. Before the
TypeScript build, it tries these paths in order:

1. Reuse matching `pkg/` files after verifying their checksums.
2. Restore matching files from the per-user cache (shared across worktrees).
3. Download an exact-hash artifact from a successful `main` push of the
repository's build workflow. PR artifacts are never accepted.
4. Compile from source with rustup (the pinned toolchain and wasm32 target
auto-install). CI/Vercel bootstrap rustup if needed; local machines need
rustup installed. Vercel keeps Rust caches under `node_modules/.cache`.

Developer downloads require `gh auth login`, `GH_TOKEN`, or `GITHUB_TOKEN`
with read access to Actions artifacts. No interactive authentication happens
during builds. Missing credentials, offline requests, expired artifacts, and
invalid downloads fall back to source compilation; downloads time out after
30 seconds. ZIP archives are read using `fflate`; no system `unzip` executable
or newer Node version is required.
Artifacts are retained for 90 days, and a new
fingerprint only becomes downloadable after a successful `main` build.

The cache lives at `$XDG_CACHE_HOME/blocknote/typst-wasm-v3` (default
`~/.cache/blocknote/typst-wasm-v3`). Override it with
`BLOCKNOTE_TYPST_CACHE_DIR`. Entries are content-addressed and can be removed
when no builds are running to reclaim disk space; there is no automatic eviction.
Set `BLOCKNOTE_TYPST_DOWNLOAD=0` to disable remote downloads while keeping local
reuse. `vp run --filter @blocknote/xl-typst-compiler build:wasm` bypasses all
caches and forces source compilation. `BLOCKNOTE_TYPST_SOURCE_ONLY=1` also
bypasses all WASM reuse; release builds use this with task caching disabled.

The v3 cache/artifact schema contains four generated files and one
`.build-manifest.json` recording the input hash and file checksums. It replaces
v2's redundant `.build-hash` metadata with a new fingerprint and namespace:
old local outputs rebuild or restore, v2 remote artifacts are not requested,
and old shared-cache entries may be removed manually. A source build removes
leftover `.build-hash` metadata from `pkg/`.

For each matching artifact, the producer trust check and archive download run
in parallel. An untrusted producer aborts/discards the speculative download;
no archive is extracted or installed before trust is established. Only one
candidate is downloaded at a time.
Downloads are staged beside `pkg/` and checksum-verified before being renamed
into place without a second output copy. The checksums
detect corruption; trust comes from the restricted workflow producer, not the
source hash alone. Simultaneous builds in one checkout are serialized. If a
process is forcibly killed, remove `node_modules/.typst-wasm.lock` inside this
package after confirming no build is still running.

On native Windows, source builds invoke wasm-pack through Node rather than a
shell shim. If Rust is missing, the build exits with rustup setup instructions
instead of attempting the Unix installer. Native Windows has not been tested
end to end.

`pkg/` is a build output (not checked in); the published npm package
includes it.

Expand Down
2 changes: 1 addition & 1 deletion packages/xl-typst-compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@
},
"scripts": {
"dev": "vp dev",
"build:wasm": "node scripts/ensure-wasm.mjs --force",
"lint": "vp lint src",
"test": "vp test --run",
"test-watch": "vp test watch",
"clean": "rimraf dist && rimraf types"
},
"devDependencies": {
"@types/node": "22.13.13",
"fflate": "^0.8.3",
"rimraf": "^5.0.10",
"rollup-plugin-webpack-stats": "^0.2.6",
"typescript": "^7.0.2",
Expand Down
Loading
Loading