From def3e5c1235b54f00e7aaf18625d2fb1439146a0 Mon Sep 17 00:00:00 2001 From: Charlie Zhang Date: Mon, 31 Aug 2026 12:54:34 +0800 Subject: [PATCH 1/2] Support demos build with npm --- .github/workflows/pages-demos.yml | 3 +++ demos/README.md | 7 ++++--- demos/assemble-pages.sh | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pages-demos.yml b/.github/workflows/pages-demos.yml index 12ab3d9..e19f802 100644 --- a/.github/workflows/pages-demos.yml +++ b/.github/workflows/pages-demos.yml @@ -25,6 +25,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 - name: Assemble site run: bash demos/assemble-pages.sh _site - uses: actions/configure-pages@v5 diff --git a/demos/README.md b/demos/README.md index 2c03fd2..b2fc5c6 100644 --- a/demos/README.md +++ b/demos/README.md @@ -23,7 +23,7 @@ Each demo should document how the caller supplies their own token. ## Catalog -Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/) (index of every client-only demo). A folder is published when it has `index.html` and no `vercel.json`. +Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/) (index of every client-only demo). A folder is published when it has `index.html` and no `vercel.json`. If the folder has `package.json`, Pages runs `npm ci && npm run build` and publishes `dist/`. | Demo | Mode | Deploy | |------|------|--------| @@ -34,7 +34,8 @@ Official static host: [https://robotemi.github.io/openapi/](https://robotemi.git 1. Add a self-contained directory under `demos/`. 2. Include a short `README.md` with purpose, setup, and how the OAT is provided. 3. Keep dependencies and scope small enough that someone else can run the demo without a private environment. -4. Client-only static demos: add `index.html` at the folder root, omit `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the folder to `https://robotemi.github.io/openapi//`. -5. Serverless demos: do not add `index.html` as a Pages app (or include `vercel.json`). Deploy as a separate Vercel project. +4. Client-only static demos (no build): add `index.html` at the folder root, omit `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the folder to `https://robotemi.github.io/openapi//`. +5. Client-only demos that use Vite/TypeScript: keep `package.json` with `npm run build` writing `dist/index.html`, set Vite `base` to `./`, omit `vercel.json`. Do not commit `node_modules/` or `dist/`. Pages builds and publishes `dist/`. +6. Serverless demos: include `vercel.json`. Deploy as a separate Vercel project. Questions about the API itself belong in the [API reference](https://openapi-docs.robotemi.com). diff --git a/demos/assemble-pages.sh b/demos/assemble-pages.sh index ca01e43..45a5902 100755 --- a/demos/assemble-pages.sh +++ b/demos/assemble-pages.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Build the GitHub Pages tree: index + each static demo as a subfolder. # A demo is published when it has index.html and no vercel.json. +# If package.json is present, run `npm ci && npm run build` and publish dist/. set -euo pipefail demos="$(cd "$(dirname "$0")" && pwd)" @@ -68,7 +69,19 @@ EOF continue ;; esac - cp -R -- "$dir" "$out/$name" + if [ -f "${dir}package.json" ]; then + echo "assemble-pages: building $name" + (cd "$dir" && npm ci && npm run build) + if [ ! -f "${dir}dist/index.html" ]; then + echo "assemble-pages: $name build produced no dist/index.html" >&2 + exit 1 + fi + mkdir -p -- "$out/$name" + cp -R -- "${dir}dist/." "$out/$name/" + touch -- "$out/$name/.nojekyll" + else + cp -R -- "$dir" "$out/$name" + fi printf '
  • %s
  • \n' "$name" "$name" done cat <<'EOF' From 22a5759669ff528f84c4dc7f9a39c8e9c9189a3c Mon Sep 17 00:00:00 2001 From: Charlie Zhang Date: Mon, 31 Aug 2026 12:57:46 +0800 Subject: [PATCH 2/2] Add PR build jobs --- .cursor/rules/demos-pages.mdc | 42 +++++++++++++++++++++++++++++ .github/workflows/pages-demos.yml | 44 +++++++++++++++++++++++-------- demos/README.md | 10 ++++--- demos/assemble-pages.sh | 22 +++++++++------- 4 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 .cursor/rules/demos-pages.mdc diff --git a/.cursor/rules/demos-pages.mdc b/.cursor/rules/demos-pages.mdc new file mode 100644 index 0000000..43d2c6a --- /dev/null +++ b/.cursor/rules/demos-pages.mdc @@ -0,0 +1,42 @@ +--- +description: OpenAPI demo publishing — token safety, Pages assemble, CI trust +globs: demos/**,.github/workflows/pages*.yml +alwaysApply: false +--- + +# Demos and GitHub Pages + +Source of truth: `demos/README.md` and `demos/assemble-pages.sh`. Keep the README inclusion rules identical to the script `if`s. + +## Token + +- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers`. +- No `sessionStorage` / `localStorage` / cookies / analytics / CDN scripts that can read the token. +- `fetch` that carries the OAT: `redirect: 'error'`. +- Do not official-host a backend that accepts someone else's OAT. + +## Assemble script (`demos/assemble-pages.sh`) + +- Guard `rm -rf -- "$out"`: canonicalize `out` (`abspath` + `normpath`), then refuse empty, `.`, `..`, `/`, leading `-`, repo root, `demos/`, or an `out` that is an ancestor of the repo. +- Demo folder names: `A–Z a–z 0–9 . _ -` only; skip the rest (they go into HTML). +- Publish when there is no `vercel.json` and either: + - no `package.json` + root `index.html` → copy the **whole folder**; + - `package.json` + `package-lock.json` (or `npm-shrinkwrap.json`) → `rm -rf dist && npm ci --no-audit --no-fund && npm run build`, publish `dist/` (must contain `dist/index.html`). Fail fast if the lockfile is missing. +- Vite: `base: './'`. Do not commit `node_modules/` or `dist/`. + +## Workflow + +- PR: assemble only, never `deploy-pages`. +- Deploy only from `master` (push or `workflow_dispatch` on that ref). +- Do not run `npm` / assemble on **fork** PRs (`head.repo.full_name != github.repository`). Same-repo PRs may assemble. +- Generic `upload-artifact` of `_site` must set `include-hidden-files: true` so `.nojekyll` reaches Pages. +- One PR should not both invent a new CI trust path and change publish layout. Split those. + +## Before opening a PR + +```bash +bash demos/assemble-pages.sh _site +npx --yes shellcheck demos/assemble-pages.sh +``` + +Then re-read `demos/README.md` against the script. Serve `_site` locally if the change is user-visible. diff --git a/.github/workflows/pages-demos.yml b/.github/workflows/pages-demos.yml index e19f802..3114a94 100644 --- a/.github/workflows/pages-demos.yml +++ b/.github/workflows/pages-demos.yml @@ -6,30 +6,52 @@ on: paths: - 'demos/**' - '.github/workflows/pages-demos.yml' + pull_request: + paths: + - 'demos/**' + - '.github/workflows/pages-demos.yml' workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write - concurrency: - group: pages + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + assemble: + # Fork PRs can run arbitrary npm scripts; only build same-repo PRs and master. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 20.x - name: Assemble site run: bash demos/assemble-pages.sh _site + - uses: actions/upload-artifact@v4 + with: + name: site + path: _site + include-hidden-files: true + + deploy: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' + needs: assemble + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: site + path: _site - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 with: diff --git a/demos/README.md b/demos/README.md index b2fc5c6..0f85240 100644 --- a/demos/README.md +++ b/demos/README.md @@ -23,7 +23,10 @@ Each demo should document how the caller supplies their own token. ## Catalog -Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/) (index of every client-only demo). A folder is published when it has `index.html` and no `vercel.json`. If the folder has `package.json`, Pages runs `npm ci && npm run build` and publishes `dist/`. +Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/). A folder is published when it has no `vercel.json` and either: + +- **No build:** root `index.html` and no `package.json` — the **whole folder** is copied (HTML, JS, README, assets). +- **npm build:** `package.json` plus `package-lock.json` (or `npm-shrinkwrap.json`). CI runs `rm -rf dist && npm ci --no-audit --no-fund && npm run build` and publishes only `dist/` (must contain `dist/index.html`). A source `index.html` is only needed if the bundler uses it (Vite does). | Demo | Mode | Deploy | |------|------|--------| @@ -34,8 +37,9 @@ Official static host: [https://robotemi.github.io/openapi/](https://robotemi.git 1. Add a self-contained directory under `demos/`. 2. Include a short `README.md` with purpose, setup, and how the OAT is provided. 3. Keep dependencies and scope small enough that someone else can run the demo without a private environment. -4. Client-only static demos (no build): add `index.html` at the folder root, omit `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the folder to `https://robotemi.github.io/openapi//`. -5. Client-only demos that use Vite/TypeScript: keep `package.json` with `npm run build` writing `dist/index.html`, set Vite `base` to `./`, omit `vercel.json`. Do not commit `node_modules/` or `dist/`. Pages builds and publishes `dist/`. +4. Client-only static demos (no build): add `index.html` at the folder root, omit `package.json` and `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the **entire folder** to `https://robotemi.github.io/openapi//`. +5. Client-only demos that use Vite/TypeScript: commit `package.json` **and** a lockfile (`package-lock.json` or `npm-shrinkwrap.json`), with `npm run build` writing `dist/index.html`. Set Vite `base` to `./`. Omit `vercel.json`. Do not commit `node_modules/` or `dist/`. Pages runs `rm -rf dist && npm ci --no-audit --no-fund && npm run build` and publishes `dist/`. 6. Serverless demos: include `vercel.json`. Deploy as a separate Vercel project. +7. Pull-request assemble (build, no publish) runs only for branches on `robotemi/openapi`. Fork PRs are not built automatically; a maintainer can check out the branch and run `bash demos/assemble-pages.sh _site` locally. Questions about the API itself belong in the [API reference](https://openapi-docs.robotemi.com). diff --git a/demos/assemble-pages.sh b/demos/assemble-pages.sh index 45a5902..51c7973 100755 --- a/demos/assemble-pages.sh +++ b/demos/assemble-pages.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Build the GitHub Pages tree: index + each static demo as a subfolder. -# A demo is published when it has index.html and no vercel.json. -# If package.json is present, run `npm ci && npm run build` and publish dist/. +# A demo is published when it has no vercel.json and either: +# - package.json + lockfile → rm -rf dist && npm ci --no-audit --no-fund && npm run build, publish dist/ +# - root index.html (no package.json) → copy the whole folder set -euo pipefail demos="$(cd "$(dirname "$0")" && pwd)" @@ -18,10 +19,7 @@ case "$out" in exit 1 ;; esac -if [ "${out#/}" = "$out" ]; then - out="$(pwd)/$out" -fi -out="${out%/}" +out="$(python3 -c 'import os, sys; print(os.path.normpath(os.path.abspath(os.path.expanduser(sys.argv[1]))))' "$out")" if [ "$out" = "$repo" ] || [ "$out" = "$demos" ]; then echo "assemble-pages: refusing to delete the repo or demos directory" >&2 exit 1 @@ -60,7 +58,6 @@ touch -- "$out/.nojekyll" EOF for dir in "$demos"/*/; do [ -d "$dir" ] || continue - [ -f "${dir}index.html" ] || continue [ -f "${dir}vercel.json" ] && continue name="$(basename "$dir")" case "$name" in @@ -70,8 +67,12 @@ EOF ;; esac if [ -f "${dir}package.json" ]; then + if [ ! -f "${dir}package-lock.json" ] && [ ! -f "${dir}npm-shrinkwrap.json" ]; then + echo "assemble-pages: $name has package.json but no package-lock.json (or npm-shrinkwrap.json); npm ci requires a lockfile" >&2 + exit 1 + fi echo "assemble-pages: building $name" - (cd "$dir" && npm ci && npm run build) + (cd "$dir" && rm -rf dist && npm ci --no-audit --no-fund && npm run build) if [ ! -f "${dir}dist/index.html" ]; then echo "assemble-pages: $name build produced no dist/index.html" >&2 exit 1 @@ -79,8 +80,11 @@ EOF mkdir -p -- "$out/$name" cp -R -- "${dir}dist/." "$out/$name/" touch -- "$out/$name/.nojekyll" - else + elif [ -f "${dir}index.html" ]; then cp -R -- "$dir" "$out/$name" + else + echo "assemble-pages: skipping $name (need index.html, or package.json + lockfile)" >&2 + continue fi printf '
  • %s
  • \n' "$name" "$name" done