diff --git a/.github/actions/setup-quartonotebookrunner-jl/action.yml b/.github/actions/setup-quartonotebookrunner-jl/action.yml new file mode 100644 index 0000000..a418f68 --- /dev/null +++ b/.github/actions/setup-quartonotebookrunner-jl/action.yml @@ -0,0 +1,79 @@ +name: 'Set up QuartoNotebookRunner' +description: > + Install QuartoNotebookRunner.jl into the @quarto named environment and start + it under xvfb-run, writing the transport file at the path Quarto reads, so + `quarto render` reuses our pre-started server. +inputs: + timeout: + description: 'Idle timeout passed to QuartoNotebookRunner.serve (seconds).' + required: false + default: '300' + screen: + description: 'xvfb-run -s screen geometry.' + required: false + default: '-screen 0 1024x768x24' + wait-seconds: + description: 'How long to wait for the transport file to appear before failing.' + required: false + default: '60' +runs: + using: 'composite' + steps: + - name: Install system dependencies for headless rendering + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev \ + libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev + + - name: Install QuartoNotebookRunner into @quarto environment + shell: bash + run: julia --project=@quarto -e 'import Pkg; Pkg.add("QuartoNotebookRunner"); Pkg.precompile()' + + - name: Start QuartoNotebookRunner server (under xvfb) + shell: bash + env: + QNR_TIMEOUT: ${{ inputs.timeout }} + XVFB_SCREEN: ${{ inputs.screen }} + QNR_WAIT_SECONDS: ${{ inputs.wait-seconds }} + run: | + set -euo pipefail + # Match Quarto's transport-file path so `quarto render` reuses our server + # (see quarto-cli julia-engine.ts: juliaTransportFile()). + if [ -n "${XDG_RUNTIME_DIR:-}" ]; then + QNR_RUNTIME_DIR="$XDG_RUNTIME_DIR/julia" + else + QNR_RUNTIME_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/quarto/julia" + fi + mkdir -p "$QNR_RUNTIME_DIR" + TRANSPORT_FILE="$QNR_RUNTIME_DIR/julia_transport.txt" + rm -f "$TRANSPORT_FILE" + export QNR_TRANSPORT_FILE="$TRANSPORT_FILE" + + cat > /tmp/start_qnr.jl < rm(transport_file; force = true)) + server = QuartoNotebookRunner.serve(; timeout = ${QNR_TIMEOUT}) + open(transport_file, "w") do io + println(io, """{"port": \$(server.port), "pid": \$(Base.Libc.getpid()), "key": "\$(server.key)"}""") + end + @info "QNR server listening on port \$(server.port)" + wait(server) + JL + + nohup xvfb-run -a -s "${XVFB_SCREEN}" \ + julia --project=@quarto /tmp/start_qnr.jl > qnr-server.log 2>&1 & + echo "QNR_PID=$!" >> "$GITHUB_ENV" + + for i in $(seq 1 "${QNR_WAIT_SECONDS}"); do + if [ -f "$TRANSPORT_FILE" ]; then + echo "Transport file ready after ${i}s:" + cat "$TRANSPORT_FILE" + exit 0 + fi + sleep 1 + done + echo "Transport file did not appear in ${QNR_WAIT_SECONDS}s; QNR server may have failed to start" + tail -100 qnr-server.log || true + exit 1 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index efb579d..517eea2 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,16 +11,28 @@ jobs: permissions: contents: write statuses: write + env: + DISPLAY: ':0' + QUARTO_JULIA_PROJECT: '@quarto' + DATADEPS_ALWAYS_ACCEPT: 'true' steps: - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 + - uses: ./.github/actions/setup-quartonotebookrunner-jl + - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 - - name: Render and Publish + + - name: Render and Publish uses: quarto-dev/quarto-actions/publish@v2 with: target: netlify - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} \ No newline at end of file + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + + - name: Show QuartoNotebookRunner server log + if: always() + run: cat qnr-server.log 2>/dev/null || true diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 72ea355..778c352 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -13,17 +13,25 @@ jobs: statuses: write pull-requests: write deployments: write + env: + DISPLAY: ':0' + QUARTO_JULIA_PROJECT: '@quarto' + DATADEPS_ALWAYS_ACCEPT: 'true' steps: - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 + - uses: ./.github/actions/setup-quartonotebookrunner-jl + - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 - - name: Render + + - name: Render uses: quarto-dev/quarto-actions/render@v2 - with: + with: to: html - name: Deploy Preview to Netlify as preview @@ -45,3 +53,7 @@ jobs: enable-commit-status: true overwrites-pull-request-comment: false timeout-minutes: 1 + + - name: Show QuartoNotebookRunner server log + if: always() + run: cat qnr-server.log 2>/dev/null || true diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..5488c87 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,3 @@ +[build] + publish = "docs" + ignore = "exit 0"