From 55f4e27f1749bc2c74029c8a8689f5a6c6314513 Mon Sep 17 00:00:00 2001 From: Jack Firth Date: Fri, 10 Jul 2026 17:13:15 -0700 Subject: [PATCH 1/2] Publish docs preview site Co-Authored-By: Claude --- .github/workflows/docs.yml | 85 ++++++++++++++++++++++++++++++++++++++ script/publish-docs.sh | 61 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100755 script/publish-docs.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..57fa75c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,85 @@ +name: Docs + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened, closed] + +# All three jobs below deploy to the same Wisp site by pulling its current +# tree, editing one subpath, and redeploying the whole thing (see +# script/publish-docs.sh) — they must not run concurrently or they'll race +# and clobber each other's changes. +concurrency: + group: docs-wisp-deploy + cancel-in-progress: false + +permissions: + contents: read + pull-requests: write + +jobs: + publish: + name: Publish docs (master) + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: Bogdanp/setup-racket@v1.5 + with: + version: stable + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: raco pkg install --batch --auto --link --name resyntax + - run: npm install -g wispctl@latest + - run: script/publish-docs.sh publish "" + env: + WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }} + + preview: + name: Publish PR preview + if: github.event_name == 'pull_request' && github.event.action != 'closed' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: Bogdanp/setup-racket@v1.5 + with: + version: stable + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: raco pkg install --batch --auto --link --name resyntax + - run: npm install -g wispctl@latest + - run: script/publish-docs.sh publish "preview/${{ github.event.pull_request.number }}" + env: + WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }} + - name: Comment preview link on PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + MARKER="" + BODY="$MARKER + Docs preview: https://resyntax.notjack.space/preview/${PR_NUMBER}/" + existing_id="$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --jq ".[] | select(.body | startswith(\"$MARKER\")) | .id" | head -n1)" + if [ -n "$existing_id" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$existing_id" -f body="$BODY" + else + gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$BODY" + fi + + preview-cleanup: + name: Remove PR preview + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm install -g wispctl@latest + - run: script/publish-docs.sh delete "preview/${{ github.event.pull_request.number }}" + env: + WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }} diff --git a/script/publish-docs.sh b/script/publish-docs.sh new file mode 100755 index 0000000..a286897 --- /dev/null +++ b/script/publish-docs.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Builds Resyntax's Scribble manual and publishes it to the resyntax-docs +# Wisp site (hosted under @claude.notjack.space), merging into whatever's +# already deployed there. +# +# wispctl deploy always replaces a site's entire tree with --path's +# contents, so a naive deploy of just the production docs (or just one PR's +# preview) would wipe out everything else on the site. This script pulls the +# current tree first and only touches the one subpath it's responsible for. +# +# Usage: +# publish-docs.sh publish # "" for the production docs at the +# # site root, "preview/802" for a PR +# publish-docs.sh delete # remove a subpath, e.g. after a PR closes +# +# Must be run from the repository root. Requires WISP_APP_PASSWORD in the +# environment and wispctl/raco on PATH. + +set -euo pipefail + +ACTION="${1:?usage: publish-docs.sh }" +SUBPATH="${2:-}" + +HANDLE="claude.notjack.space" +SITE="resyntax-docs" + +if [ -z "${WISP_APP_PASSWORD:-}" ]; then + echo "WISP_APP_PASSWORD must be set" >&2 + exit 1 +fi + +WORKDIR="$(mktemp -d)" +trap 'rm -rf "$WORKDIR"' EXIT + +SITE_DIR="$WORKDIR/site" +mkdir -p "$SITE_DIR" + +# Ignore failure: the site may not exist yet on the very first publish. +wispctl pull "$HANDLE" --site "$SITE" --path "$SITE_DIR" --password "$WISP_APP_PASSWORD" || true + +TARGET_DIR="$SITE_DIR${SUBPATH:+/$SUBPATH}" + +case "$ACTION" in + delete) + rm -rf "$TARGET_DIR" + ;; + publish) + BUILD_DIR="$WORKDIR/build" + raco scribble ++main-xref-in --redirect-main https://docs.racket-lang.org/ \ + --htmls --dest "$BUILD_DIR" main.scrbl + rm -rf "$TARGET_DIR" + mkdir -p "$TARGET_DIR" + cp -r "$BUILD_DIR/main/." "$TARGET_DIR/" + ;; + *) + echo "Unknown action: $ACTION (expected publish or delete)" >&2 + exit 1 + ;; +esac + +wispctl deploy "$HANDLE" --path "$SITE_DIR" --site "$SITE" --yes --password "$WISP_APP_PASSWORD" From d4ec5b2b9d289f435939d64b4740c964a54eae10 Mon Sep 17 00:00:00 2001 From: Jack Firth Date: Fri, 10 Jul 2026 18:41:25 -0700 Subject: [PATCH 2/2] Update publish-docs.sh --- script/publish-docs.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/script/publish-docs.sh b/script/publish-docs.sh index a286897..42f00b3 100755 --- a/script/publish-docs.sh +++ b/script/publish-docs.sh @@ -48,6 +48,23 @@ case "$ACTION" in BUILD_DIR="$WORKDIR/build" raco scribble ++main-xref-in --redirect-main https://docs.racket-lang.org/ \ --htmls --dest "$BUILD_DIR" main.scrbl + # Wisp serves directory URLs that lack a trailing slash by returning the + # index page directly instead of redirecting to the slash form, which + # breaks the page's relative links (the browser resolves them one + # directory too high). Scribble has no option for this, so inject a + # snippet that bounces slash-less directory URLs to the slash form. + python3 - "$BUILD_DIR/main/index.html" <<'EOF' +import sys +path = sys.argv[1] +snippet = ('') +with open(path, encoding="utf-8") as f: + html = f.read() +assert "" in html, "no tag found in " + path +with open(path, "w", encoding="utf-8") as f: + f.write(html.replace("", "" + snippet, 1)) +EOF rm -rf "$TARGET_DIR" mkdir -p "$TARGET_DIR" cp -r "$BUILD_DIR/main/." "$TARGET_DIR/"