From 41d612989c053eaab2340676ac8a98609ba14b84 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Fri, 12 Jun 2026 10:04:59 +1000 Subject: [PATCH 1/2] ci: per-PR rendered previews on GitHub Pages Add preview.yml: on each same-repo PR, build the theme, statically build the real QuantEcon/lecture-python-programming lectures with it (myst build --html, BASE_URL set to the Pages subpath), and deploy to the gh-pages branch under pr-preview/pr-/ via rossjrw/pr-preview-action (sticky link comment, teardown on close). Self-contained on GITHUB_TOKEN - the org-secrets blocker that deferred the Netlify approach is gone. The content repo is a legacy Jupyter Book, so the build pipes a confirmation into `myst init` to run the JB->MyST upgrade at build time - the preview doubles as a migration-readiness check. Content is cloned at full depth so the git-metadata plugin (PR #83) renders real per-page history once it lands; a guard skips plugin injection on branches that predate it. Static export is the production path the Playwright harness (live `myst start`) never exercises; the preview is qualitative and non-gating - Playwright remains the only gate. Validated end-to-end locally: JB upgrade under non-TTY stdin, yq config patching, 4.6s/45MB build, BASE_URL-prefixed assets, SSR'd history header on real git data. gh-pages was seeded with a root .nojekyll (Pages would otherwise drop the underscore-prefixed build/_assets/ paths) and a landing index.html; Pages serves from the gh-pages branch root. Co-Authored-By: Claude Fable 5 --- .github/workflows/preview.yml | 99 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 3 ++ PLAN.md | 12 +++-- 3 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/preview.yml diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 00000000..06e5bb09 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,99 @@ +# PR preview on GitHub Pages — self-contained (GITHUB_TOKEN only, no org secrets). +# +# Builds a real lecture site (QuantEcon/lecture-python-programming) with the +# PR's theme via a static `myst build --html` — the production export path the +# Playwright harness (live `myst start`) never exercises — and deploys it to +# the gh-pages branch under pr-preview/pr-/. A sticky PR comment links the +# preview; closing the PR tears the subdirectory down. +# +# The content repo is a legacy Jupyter Book (_config.yml/_toc.yml): `myst init` +# performs the JB→MyST upgrade at build time, so the preview doubles as a +# continuous migration-readiness check for the lecture content (PLAN.md open +# question 4). Content is cloned at full depth so the git-metadata plugin +# (plugins/git-metadata.mjs, PR #83) renders real per-page history. +name: PR Preview + +on: + pull_request: + types: [opened, reopened, synchronize, closed] + +concurrency: + group: pr-preview-${{ github.event.number }} + cancel-in-progress: true + +permissions: + contents: write # push the rendered site to the gh-pages branch + pull-requests: write # sticky preview-link comment + +env: + CONTENT_REPO: QuantEcon/lecture-python-programming + CONTENT_DIR: lectures + +jobs: + preview: + name: Build & deploy preview (gh-pages) + # Same-repo PRs only, like update-snapshots.yml (fork PRs get a read-only + # GITHUB_TOKEN and could not deploy anyway). + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + if: github.event.action != 'closed' + with: + node-version: 24 + cache: npm + - name: Install dependencies + if: github.event.action != 'closed' + run: npm ci + - name: Install mystmd CLI + if: github.event.action != 'closed' + run: npm install -g mystmd + # Builds the candidate theme from this branch into .deploy/quantecon-theme. + - name: Build theme template + if: github.event.action != 'closed' + run: make build-theme + - name: Checkout lecture content (full history for git metadata) + if: github.event.action != 'closed' + uses: actions/checkout@v4 + with: + repository: ${{ env.CONTENT_REPO }} + path: preview-content + fetch-depth: 0 + - name: Convert content to MyST and point it at the PR theme + if: github.event.action != 'closed' + working-directory: preview-content/${{ env.CONTENT_DIR }} + env: + THEME_DIR: ${{ github.workspace }}/.deploy/quantecon-theme + PLUGIN: ${{ github.workspace }}/plugins/git-metadata.mjs + CONTENT_GITHUB: https://github.com/${{ env.CONTENT_REPO }} + run: | + # `myst init` upgrades the legacy Jupyter Book to myst.yml. With + # piped (non-TTY) stdin it exits non-zero on its follow-up + # "run myst start now?" prompt after the config is written — + # tolerate that, then assert the upgrade actually happened. + printf 'y\nn\n' | myst init || true + test -f myst.yml + yq -i '.site.template = env(THEME_DIR)' myst.yml + yq -i '.project.github = env(CONTENT_GITHUB)' myst.yml + # The git-metadata plugin ships with the theme from PR #83 onward; + # guard so previews still build on branches that predate it. + if [ -f "$PLUGIN" ]; then + yq -i '.project.plugins = [env(PLUGIN)]' myst.yml + fi + - name: Build static site + if: github.event.action != 'closed' + working-directory: preview-content/${{ env.CONTENT_DIR }} + env: + # Must match the Pages subpath the deploy step publishes to. + BASE_URL: /quantecon-theme.mystmd/pr-preview/pr-${{ github.event.number }} + run: myst build --html + # auto mode: deploys on opened/reopened/synchronize, removes the + # pr-preview/pr-/ subdirectory on closed, and maintains the sticky + # comment. Root .nojekyll on gh-pages (seeded once) keeps Pages from + # eating the underscore-prefixed asset paths (build/_assets/...). + - name: Deploy / teardown preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: preview-content/${{ env.CONTENT_DIR }}/_build/html + action: auto diff --git a/CHANGELOG.md b/CHANGELOG.md index 767242c9..537d5c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Per-PR rendered previews on GitHub Pages (`.github/workflows/preview.yml`): each PR statically builds the real `lecture-python-programming` lectures with the candidate theme (`myst build --html`, including the build-time Jupyter Book → MyST upgrade) and publishes to `gh-pages` at `pr-preview/pr-/` with a sticky link comment and teardown on close. Self-contained on `GITHUB_TOKEN` — no external preview service. + ### Removed - Retire the `make deploy` / `deploy-theme` targets — releases ship exclusively via the tag-triggered GitHub Release workflow, and the legacy build repo (`QuantEcon/quantecon-theme`) is archived. `make build-theme` now assembles the bundle locally instead of cloning the archived repo (so the CI test harness no longer depends on it), and a new `make build-zip` produces the release-equivalent zip for local artifact testing ([#81](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/81)). diff --git a/PLAN.md b/PLAN.md index ba7da94c..e864dc99 100644 --- a/PLAN.md +++ b/PLAN.md @@ -193,15 +193,21 @@ upstream is heading. - [x] Triaged & consolidated the ~20 Dependabot PRs into a single lockfile refresh ([#55](https://github.com/QuantEcon/quantecon-theme-src/pull/55)–58; `npm audit` 68 → 45). The remaining findings need the Remix v2 migration ([#28]) — still to schedule. -- [ ] Stand up a **visual preview / smoke test** against a real lecture repo (the book-theme +- [x] Stand up a **visual preview / smoke test** against a real lecture repo (the book-theme uses `quantecon-book-theme-fixtures` + Playwright + Netlify previews — a lighter MyST equivalent would de-risk every later phase). **Status:** the CI gate landed in [#78](https://github.com/QuantEcon/quantecon-theme-src/pull/78) — a `visual` job pixel-diffs the fixture on every PR (desktop + mobile + sidebar-open), with `/update-snapshots` comment-triggered baseline refresh, mirroring the book-theme's - setup. **Remaining:** a Netlify-style rendered preview for reviewers (needs org - secrets) and a smoke test against real lecture content (e.g. `lecture-wasm`). + setup. The rendered preview is **GitHub Pages, not Netlify** (self-contained, + `GITHUB_TOKEN` only — the org-secrets blocker is gone): `preview.yml` builds + `lecture-python-programming` with the PR's theme via static `myst build --html` + (the export/hydration path Playwright's live `myst start` never exercises), + deploys to `gh-pages` under `pr-preview/pr-/`, sticky-comments the link, and + tears down on close. The content repo is still a legacy Jupyter Book, so the + build-time `myst init` upgrade doubles as a migration-readiness check (open + question 4). Playwright remains the only gate; the preview is qualitative. **Decisions (resolved):** (1) **versioning** — manual Keep a Changelog + git tags (Changesets dropped); (2) **consumer URL** — pinned per-lecture tag URLs From aac630927cade44a173223ad825dd2c93887aafa Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Fri, 12 Jun 2026 10:12:49 +1000 Subject: [PATCH 2/2] ci: bump preview.yml actions to Node 24 runtimes Same checkout/setup-node v4 -> v5 bump as #85 (lowest node24 majors, pure runtime ports). rossjrw/pr-preview-action@v1 stays: its internal JamesIves/marocchino pins are upstream's to update and run under the forced node24 runtime from 2026-06-16. Co-Authored-By: Claude Fable 5 --- .github/workflows/preview.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 06e5bb09..727fb84e 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -37,8 +37,8 @@ jobs: if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 if: github.event.action != 'closed' with: node-version: 24 @@ -55,7 +55,7 @@ jobs: run: make build-theme - name: Checkout lecture content (full history for git metadata) if: github.event.action != 'closed' - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: ${{ env.CONTENT_REPO }} path: preview-content