From d2413d895bd18fc05b3af95352908604f7bf385c Mon Sep 17 00:00:00 2001 From: dylan-sutton-chavez Date: Sun, 31 May 2026 01:11:36 -0600 Subject: [PATCH 1/2] CI: Unify pipeline into single main.yml with composite actions. --- .github/README.md | 101 +++++++++++++ .github/actions/cdn-deploy/action.yml | 65 +++++++++ .github/actions/cli/action.yml | 79 +++++++++++ .github/actions/compiler/action.yml | 139 ++++++++++++++++++ .github/actions/demo/action.yml | 48 +++++++ .github/actions/docs/action.yml | 44 ++++++ .github/actions/host/action.yml | 45 ++++++ .github/actions/runtime/action.yml | 45 ++++++ .github/actions/std/action.yml | 142 +++++++++++++++++++ .github/workflows/README.md | 71 ---------- .github/workflows/_check.yml | 37 ----- .github/workflows/_demo.yml | 48 ------- .github/workflows/_runtime.yml | 39 ----- .github/workflows/_runtime_check.yml | 59 -------- .github/workflows/_wasm.yml | 86 ----------- .github/workflows/cli.yml | 118 ---------------- .github/workflows/docs.yml | 51 ------- .github/workflows/host.yml | 111 --------------- .github/workflows/main.yml | 165 ++++++++++++++++++++++ .github/workflows/pipeline.yml | 50 ------- .github/workflows/std.yml | 196 -------------------------- 21 files changed, 873 insertions(+), 866 deletions(-) create mode 100644 .github/README.md create mode 100644 .github/actions/cdn-deploy/action.yml create mode 100644 .github/actions/cli/action.yml create mode 100644 .github/actions/compiler/action.yml create mode 100644 .github/actions/demo/action.yml create mode 100644 .github/actions/docs/action.yml create mode 100644 .github/actions/host/action.yml create mode 100644 .github/actions/runtime/action.yml create mode 100644 .github/actions/std/action.yml delete mode 100644 .github/workflows/README.md delete mode 100644 .github/workflows/_check.yml delete mode 100644 .github/workflows/_demo.yml delete mode 100644 .github/workflows/_runtime.yml delete mode 100644 .github/workflows/_runtime_check.yml delete mode 100644 .github/workflows/_wasm.yml delete mode 100644 .github/workflows/cli.yml delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/host.yml create mode 100644 .github/workflows/main.yml delete mode 100644 .github/workflows/pipeline.yml delete mode 100644 .github/workflows/std.yml diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 00000000..93cb0355 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,101 @@ +# Edge Python CI/CD + +One workflow, [`main.yml`](main.yml), drives the whole monorepo, so the Actions tab +shows a single "CI / CD" run per push/PR with every job as a node in one graph. Each +package's logic lives in a **composite action** under [`../actions/`](../actions); +`main.yml` only wires the dependency graph. The composite actions are not workflows +and do not appear in the Actions tab. + +``` +compiler-check ┐ +runtime-lint ─┴─► compiler ─► runtime ─┬─► host (matrix) ─┐ + └─► std (matrix) ─┤ +cli-release (matrix, starts at t=0) ───────────────────────┴─► cli-test + │ (push to main) + cdn ◄── demo ◄── docs ◄───────────┘ + (docs builds on every run; deploys only on push to main) +``` + +`compiler-check`, `runtime-lint` and `cli-release` start at t=0. If any job fails the +dependents never run (`needs:`), so a red build stops the deploys, including the docs +deploy. The +`host` and `std` matrices use `fail-fast: false` so one capability / package failure +still reports the others. `cli-release` is the slow heavy build, so it starts +immediately and `cli-test` waits on `host`, `std`, and the release artifacts. + +## Composite actions + +| Action | Inputs | Role | +|--------|--------|------| +| `compiler` | `mode: check\|build` | check: `cargo shear` + clippy (host and wasm targets). build: build + optimize `compiler_lib.wasm`, test, upload the artifact (and attach it to the GitHub Release on tags) | +| `runtime` | `mode: lint\|test` | lint: `deno lint runtime/`. test: Deno + Playwright suite (Chromium driving `createWorker` against the CDN wasm) | +| `host` | `capability` | Deno-lints and smoke-tests one capability (`dom`, `network`, `storage`, `time`) in headless Chromium. All JS, no release | +| `std` | `package` | Clippy + build + optimize + corpus test for one stdpkg (`json`, `re`, `math` as wasm; `test` is pure Edge Python, so it skips the wasm build and only runs the corpus). Stages `.wasm` / `.py`. No release | +| `cli` | `mode: release\|test`, `target` | release: lint/check (once) + `cargo build --release` per target → tarball artifact. test: `cargo test` (drives a real Chromium) | +| `demo` | CF token + account | Hashes deps into `version.json` (cache-busting), builds Tailwind, deploys `demo/` to `edge-python-demo` | +| `docs` | `deploy`, CF token + account | `npm ci` + `next build` static export (`docs/out`, sitemap via `postbuild`). Deploys to `edge-python-docs` only when `deploy=true` | +| `cdn-deploy` | CF token + account | Pulls every artifact, stages `./compiler ./runtime ./std ./host ./cli`, one `wrangler pages deploy` to `edge-python-cdn` | + +## Cloudflare Pages + +Three **Direct Upload** projects. Actions push prebuilt directories via +`wrangler pages deploy`; Cloudflare doesn't clone or build. + +| Project | Source | Production URL | +|---------|--------|----------------| +| `edge-python-cdn` | `_site/{compiler,runtime,std,host,cli}` (consolidates the old per-package `-runtime` / `-host` / `-std` projects) | `https://edge-python-cdn.pages.dev` | +| `edge-python-demo` | `demo/` (wasm hashed for `version.json`, not bundled) | `https://edge-python-demo.pages.dev` | +| `edge-python-docs` | `docs/out` (Nextra static export) | `https://edgepython.com` (custom domain; also `https://edge-python-docs.pages.dev`) | + +All deploys run **only on pushes to `main`** and are pinned to the production `main` +branch. PRs and tags never deploy; the next `main` push refreshes the projects. + +### Cloudflare and GitHub setup + +```bash +# Wrangler CLI (Node 22+) +npx wrangler login +npx wrangler pages project create edge-python-cdn --production-branch=main +npx wrangler pages project create edge-python-demo --production-branch=main +npx wrangler pages project create edge-python-docs --production-branch=main +``` + +`edge-python-docs` serves `edgepython.com` (replacing the old Mintlify docs): after +the first deploy, add `edgepython.com` as a custom domain on the project +(Pages -> Custom domains) and remove it from Mintlify. + +Repo secrets (*Settings -> Secrets and variables -> Actions*): + +- `CLOUDFLARE_API_TOKEN`, `Account -> Cloudflare Pages -> Edit`. Create via dashboard: . +- `CLOUDFLARE_ACCOUNT_ID`, from `npx wrangler whoami` or any dashboard sidebar. + +Rotate: create new token -> update secret -> revoke old token. + +## Releases + +Pushing a `v*` tag runs the pipeline; the `compiler` build job uploads +`compiler_lib.wasm` to the matching Release. Tag must match workspace version. + +1. Bump `version` under `[workspace.package]` in root `Cargo.toml` (every crate inherits via `version.workspace = true`). Run `cargo check` to refresh `Cargo.lock`, commit. +2. Tag and push: + +```bash +git tag v0.1.0 +git push origin v0.1.0 +``` + +On tag push: `compiler-check` lints, then the `compiler` build job optimizes the +artifact and attaches it to a fresh Release with auto-generated notes. The CDN, demo +and docs deploys do not run on tags; they already deployed from the preceding `main` push. + +Nothing is published to crates.io, distribution is the `.wasm` on the Release. +`starter-module` carries its own version and isn't bumped with the workspace. + +Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares +`links = "compiler_lib"` and `compiler/build.rs` downloads +`/releases/download/v/compiler_lib.wasm` into `OUT_DIR`. +Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see +[root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`. + +Gated behind the default-on `prebuilt` feature. Producer-side compiler steps pass +`--no-default-features` to avoid fetching the asset that this same pipeline uploads later. diff --git a/.github/actions/cdn-deploy/action.yml b/.github/actions/cdn-deploy/action.yml new file mode 100644 index 00000000..61cc0d84 --- /dev/null +++ b/.github/actions/cdn-deploy/action.yml @@ -0,0 +1,65 @@ +name: CDN deploy +description: Stage every package output under one tree and deploy it to edge-python-cdn. + +inputs: + cloudflare-api-token: + description: Cloudflare API token. + required: true + cloudflare-account-id: + description: Cloudflare account id. + required: true + +runs: + using: composite + steps: + - name: Download compiler wasm + uses: actions/download-artifact@v8 + with: + name: compiler_lib_wasm + path: /tmp/compiler/ + + - name: Download std artifacts + uses: actions/download-artifact@v8 + with: + pattern: dist-* + path: /tmp/std/ + merge-multiple: true + + - name: Download cli artifacts + uses: actions/download-artifact@v8 + with: + pattern: edge-* + path: /tmp/cli/ + merge-multiple: true + + - name: Stage site + shell: bash + run: | + mkdir -p _site/compiler _site/runtime _site/std _site/host _site/cli + + # compiler: the wasm module. + cp /tmp/compiler/compiler_lib.wasm _site/compiler/ + + # runtime: JS sources plus the wasm it bundles. + cp -r runtime/. _site/runtime/ + cp /tmp/compiler/compiler_lib.wasm _site/runtime/ + + # std: .wasm / .py. + cp -r /tmp/std/. _site/std/ + + # host: flatten each capability's src/ into _site/host//. + for dir in host/*/src; do + cap="$(basename "$(dirname "$dir")")" + mkdir -p "_site/host/$cap" + cp -r "$dir"/. "_site/host/$cap/" + done + + # cli: release tarballs. + cp -r /tmp/cli/. _site/cli/ + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ inputs.cloudflare-api-token }} + accountId: ${{ inputs.cloudflare-account-id }} + command: pages deploy _site --project-name=edge-python-cdn --branch=main diff --git a/.github/actions/cli/action.yml b/.github/actions/cli/action.yml new file mode 100644 index 00000000..ff5c5dab --- /dev/null +++ b/.github/actions/cli/action.yml @@ -0,0 +1,79 @@ +name: CLI +description: cli/ is its own workspace. release = lint/check + build per target; test = cargo test. + +inputs: + mode: + description: "release | test" + required: true + target: + description: Rust target triple (release mode). + required: false + default: "" + +runs: + using: composite + steps: + - name: Toolchain (release) + if: inputs.mode == 'release' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ inputs.target }} + components: clippy + + - name: Install musl tools + if: inputs.mode == 'release' && contains(inputs.target, 'linux-musl') + shell: bash + run: sudo apt-get update && sudo apt-get install -y musl-tools + + - name: Cache Rust (release) + if: inputs.mode == 'release' + uses: Swatinem/rust-cache@v2 + with: + workspaces: cli -> cli/target + key: ${{ inputs.target }} + + # Lint + check once, anchored to the linux x86 target so the matrix doesn't repeat it. + - name: Clippy and check + if: inputs.mode == 'release' && inputs.target == 'x86_64-unknown-linux-musl' + shell: bash + working-directory: cli + run: | + cargo clippy --release --target "${{ inputs.target }}" -- -D warnings + cargo check --release --target "${{ inputs.target }}" + + - name: Build release + if: inputs.mode == 'release' + shell: bash + working-directory: cli + run: cargo build --release --target "${{ inputs.target }}" + + - name: Tar + if: inputs.mode == 'release' + shell: bash + working-directory: cli + run: tar -C "target/${{ inputs.target }}/release" -czf "edge-${{ inputs.target }}.tar.gz" edge + + - name: Upload release artifact + if: inputs.mode == 'release' + uses: actions/upload-artifact@v6 + with: + name: edge-${{ inputs.target }} + path: cli/edge-${{ inputs.target }}.tar.gz + retention-days: 1 + + - name: Toolchain (test) + if: inputs.mode == 'test' + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust (test) + if: inputs.mode == 'test' + uses: Swatinem/rust-cache@v2 + with: + workspaces: cli -> cli/target + + # Drives a real Chromium; ubuntu-latest ships google-chrome-stable. + - name: Test + if: inputs.mode == 'test' + shell: bash + working-directory: cli + run: cargo test diff --git a/.github/actions/compiler/action.yml b/.github/actions/compiler/action.yml new file mode 100644 index 00000000..e5d76e72 --- /dev/null +++ b/.github/actions/compiler/action.yml @@ -0,0 +1,139 @@ +name: Compiler +description: Rust compiler crate. check = shear + clippy; build = wasm build/opt + test + upload. + +inputs: + mode: + description: "check | build" + required: true + github-token: + description: Token for the tag Release upload (build mode, tags only). + required: false + default: "" + +runs: + using: composite + steps: + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + + - name: Toolchain (clippy) + if: inputs.mode == 'check' + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Install cargo-shear + if: inputs.mode == 'check' + shell: bash + run: cargo install cargo-shear + + # Detects declared but unused dependencies across the workspace. + - name: Shear + if: inputs.mode == 'check' + shell: bash + run: cargo shear + + # --no-default-features skips the prebuilt wasm download in build.rs. + - name: Clippy (host) + if: inputs.mode == 'check' + shell: bash + run: cargo clippy --all-targets --no-default-features -- -D warnings + + - name: Install wasm target (check) + if: inputs.mode == 'check' + shell: bash + run: rustup target add wasm32-unknown-unknown + + - name: Clippy (wasm) + if: inputs.mode == 'check' + shell: bash + run: cargo clippy --lib --target wasm32-unknown-unknown -p edge-python -p slugify-mod -- -D warnings + + # build-std needs nightly plus rust-src. + - name: Toolchain (nightly) + if: inputs.mode == 'build' + uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + + - name: Install wasm target (build) + if: inputs.mode == 'build' + shell: bash + run: rustup target add wasm32-unknown-unknown + + # apt ships an old binaryen, so fetch the upstream release. + - name: Install wasm-opt + if: inputs.mode == 'build' + shell: bash + run: | + curl -sSL https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-linux.tar.gz \ + | tar -xz --strip-components=2 -C /usr/local/bin binaryen-version_121/bin/wasm-opt + wasm-opt --version + + - name: Build + if: inputs.mode == 'build' + shell: bash + run: | + RUSTFLAGS="-Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort" \ + cargo +nightly build \ + --target wasm32-unknown-unknown \ + --lib \ + --release \ + -p edge-python \ + -Z build-std=std,panic_abort + + - name: Size (unoptimized) + if: inputs.mode == 'build' + shell: bash + run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm + + # Two passes: -Oz with traps-never-happen, then reflatten for a fresh CFG. + - name: Optimize + if: inputs.mode == 'build' + shell: bash + run: | + INPUT=target/wasm32-unknown-unknown/release/compiler_lib.wasm + + wasm-opt -Oz --converge \ + --generate-global-effects \ + --strip-debug --strip-producers \ + --enable-bulk-memory-opt \ + --enable-nontrapping-float-to-int \ + --enable-sign-ext \ + -tnh \ + -o /tmp/wasm_stage1.wasm "$INPUT" + + wasm-opt --flatten --rereloop -Oz -Oz \ + --enable-bulk-memory-opt \ + --enable-nontrapping-float-to-int \ + --enable-sign-ext \ + -o "$INPUT" /tmp/wasm_stage1.wasm + + rm /tmp/wasm_stage1.wasm + + - name: Size (optimized) + if: inputs.mode == 'build' + shell: bash + run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm + + - name: Test + if: inputs.mode == 'build' + shell: bash + run: cargo test -p edge-python --no-default-features + + - name: Upload wasm artifact + if: inputs.mode == 'build' + uses: actions/upload-artifact@v6 + with: + name: compiler_lib_wasm + path: target/wasm32-unknown-unknown/release/compiler_lib.wasm + retention-days: 1 + + - name: Upload WASM Release + if: inputs.mode == 'build' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + token: ${{ inputs.github-token }} + files: target/wasm32-unknown-unknown/release/compiler_lib.wasm + fail_on_unmatched_files: true + generate_release_notes: true diff --git a/.github/actions/demo/action.yml b/.github/actions/demo/action.yml new file mode 100644 index 00000000..da5bd87c --- /dev/null +++ b/.github/actions/demo/action.yml @@ -0,0 +1,48 @@ +name: Demo deploy +description: Hash deps into version.json, build Tailwind, deploy demo/ to edge-python-demo. + +inputs: + cloudflare-api-token: + description: Cloudflare API token. + required: true + cloudflare-account-id: + description: Cloudflare account id. + required: true + +runs: + using: composite + steps: + - uses: actions/setup-node@v6 + with: + node-version: "22" + + # Downloaded for hashing only; the demo loads the wasm from the CDN at runtime. + - name: Download wasm artifact + uses: actions/download-artifact@v8 + with: + name: compiler_lib_wasm + path: /tmp/wasm/ + + # Hash compiler wasm + runtime JS + demo Python entries for cache-busting. + - name: Write version manifest + shell: bash + run: | + HASH=$( { sha256sum /tmp/wasm/compiler_lib.wasm; \ + find runtime -type f | LC_ALL=C sort | xargs sha256sum; \ + find demo/runtime -type f | LC_ALL=C sort | xargs sha256sum; \ + } | sha256sum | cut -c1-12) + printf '{"v":"%s"}\n' "$HASH" > demo/version.json + + - name: Build Tailwind CSS + shell: bash + working-directory: demo + run: | + echo "@tailwind base;@tailwind components;@tailwind utilities;" \ + | npx tailwindcss@3 --input - --output tailwind.css --minify + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ inputs.cloudflare-api-token }} + accountId: ${{ inputs.cloudflare-account-id }} + command: pages deploy demo --project-name=edge-python-demo --branch=main diff --git a/.github/actions/docs/action.yml b/.github/actions/docs/action.yml new file mode 100644 index 00000000..4c73f3d1 --- /dev/null +++ b/.github/actions/docs/action.yml @@ -0,0 +1,44 @@ +name: Docs build & deploy +description: Build the Nextra static export (docs/out); deploy to edge-python-docs when deploy=true. + +inputs: + deploy: + description: true to deploy after a successful build. + required: false + default: "false" + cloudflare-api-token: + description: Cloudflare API token (deploy only). + required: false + default: "" + cloudflare-account-id: + description: Cloudflare account id (deploy only). + required: false + default: "" + +runs: + using: composite + steps: + - uses: actions/setup-node@v6 + with: + node-version: "22" + cache: npm + cache-dependency-path: docs/package-lock.json + + # Reproducible install from the committed lockfile. + - name: Install + shell: bash + working-directory: docs + run: npm ci + + - name: Build + shell: bash + working-directory: docs + run: npm run build + + - name: Deploy to Cloudflare Pages + if: inputs.deploy == 'true' + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ inputs.cloudflare-api-token }} + accountId: ${{ inputs.cloudflare-account-id }} + command: pages deploy docs/out --project-name=edge-python-docs --branch=main diff --git a/.github/actions/host/action.yml b/.github/actions/host/action.yml new file mode 100644 index 00000000..625cb490 --- /dev/null +++ b/.github/actions/host/action.yml @@ -0,0 +1,45 @@ +name: Host capability +description: Deno-lint and smoke-test one host capability (all JS) in Chromium. No release. + +inputs: + capability: + description: "dom | network | storage | time" + required: true + +runs: + using: composite + steps: + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Cache Deno modules + uses: actions/cache@v5 + with: + path: ~/.cache/deno + key: deno-${{ runner.os }}-${{ hashFiles('host/**/deno.json', 'host/**/deno.lock') }} + restore-keys: deno-${{ runner.os }}- + + - name: Lint src/ + shell: bash + working-directory: host/${{ inputs.capability }} + run: deno lint src/ + + # ~150MB Chromium; cached so only the first run pays the download. + - name: Cache Playwright browsers + uses: actions/cache@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-chromium + + - name: Install Chromium + shell: bash + run: deno run -A npm:playwright install --with-deps chromium + + # HOSTCAP narrows test discovery to this capability's corpus. + - name: Test tests/ + shell: bash + working-directory: host + env: + HOSTCAP: ${{ inputs.capability }} + run: deno test --allow-all tests/ diff --git a/.github/actions/runtime/action.yml b/.github/actions/runtime/action.yml new file mode 100644 index 00000000..beb31bbe --- /dev/null +++ b/.github/actions/runtime/action.yml @@ -0,0 +1,45 @@ +name: Runtime +description: Deno/JS runtime. lint = deno lint; test = Deno + Playwright suite. + +inputs: + mode: + description: "lint | test" + required: true + +runs: + using: composite + steps: + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Cache Deno modules + uses: actions/cache@v5 + with: + path: ~/.cache/deno + key: deno-${{ runner.os }}-${{ hashFiles('**/deno.json', '**/deno.lock') }} + restore-keys: deno-${{ runner.os }}- + + - name: Lint + if: inputs.mode == 'lint' + shell: bash + run: deno lint runtime/ + + # ~150MB Chromium; cached so only the first run pays the download. + - name: Cache Playwright browsers + if: inputs.mode == 'test' + uses: actions/cache@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-chromium + + # --with-deps installs the OS libs Chromium needs on Linux. + - name: Install Chromium + if: inputs.mode == 'test' + shell: bash + run: deno run -A npm:playwright install --with-deps chromium + + - name: Test + if: inputs.mode == 'test' + shell: bash + run: deno test --allow-all runtime/tests/runtime.test.js diff --git a/.github/actions/std/action.yml b/.github/actions/std/action.yml new file mode 100644 index 00000000..6f93a040 --- /dev/null +++ b/.github/actions/std/action.yml @@ -0,0 +1,142 @@ +name: Std package +description: Lint, build/optimize (wasm), test one stdpkg, then stage its artifact. No release. + +inputs: + package: + description: "json | re | math | test" + required: true + +runs: + using: composite + steps: + # Stable for clippy, nightly for the build-std wasm build. + - name: Toolchain (stable) + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + components: clippy + + - name: Toolchain (nightly) + uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-unknown-unknown + components: rust-src + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Cache Cargo + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + std/${{ inputs.package }}/target + key: cargo-std-${{ runner.os }}-${{ inputs.package }}-${{ hashFiles(format('std/{0}/Cargo.toml', inputs.package)) }} + restore-keys: cargo-std-${{ runner.os }}-${{ inputs.package }}- + + - name: Cache Deno modules + uses: actions/cache@v5 + with: + path: ~/.cache/deno + key: deno-${{ runner.os }}-${{ hashFiles('std/**/deno.json', 'std/**/deno.lock') }} + restore-keys: deno-${{ runner.os }}- + + - name: Cache Playwright browsers + uses: actions/cache@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-chromium + + - name: Install Chromium + shell: bash + run: deno run -A npm:playwright install --with-deps chromium + + # apt ships an old binaryen, so fetch the upstream release. + - name: Install wasm-opt + if: inputs.package != 'test' + shell: bash + run: | + curl -sSL "https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-linux.tar.gz" \ + | tar -xz --strip-components=2 -C /usr/local/bin "binaryen-version_121/bin/wasm-opt" + wasm-opt --version + + # Lint only the cdylib; --all-targets clashes with its panic handler. + - name: Clippy + if: inputs.package != 'test' + shell: bash + working-directory: std/${{ inputs.package }} + run: cargo +stable clippy --release --target wasm32-unknown-unknown -- -D warnings + + # `test` is pure Edge Python (entry.py): no crate to build. + - name: Build + if: inputs.package != 'test' + shell: bash + working-directory: std/${{ inputs.package }} + run: | + RUSTFLAGS="-Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort" \ + cargo +nightly build \ + --target wasm32-unknown-unknown \ + --lib --release \ + -Z build-std=std,panic_abort + + - name: Size (unoptimized) + if: inputs.package != 'test' + shell: bash + run: ls -lh "std/${{ inputs.package }}/target/wasm32-unknown-unknown/release/${{ inputs.package }}.wasm" + + # Two passes: -Oz with traps-never-happen, then reflatten for a fresh CFG. + - name: Optimize + if: inputs.package != 'test' + shell: bash + env: + WASM: std/${{ inputs.package }}/target/wasm32-unknown-unknown/release/${{ inputs.package }}.wasm + run: | + wasm-opt -Oz --converge \ + --generate-global-effects \ + --strip-debug --strip-producers \ + --enable-bulk-memory-opt \ + --enable-nontrapping-float-to-int \ + --enable-sign-ext \ + -tnh \ + -o /tmp/wasm_stage1.wasm "$WASM" + + wasm-opt --flatten --rereloop -Oz -Oz \ + --enable-bulk-memory-opt \ + --enable-nontrapping-float-to-int \ + --enable-sign-ext \ + -o "$WASM" /tmp/wasm_stage1.wasm + + rm /tmp/wasm_stage1.wasm + + - name: Size (optimized) + if: inputs.package != 'test' + shell: bash + run: ls -lh "std/${{ inputs.package }}/target/wasm32-unknown-unknown/release/${{ inputs.package }}.wasm" + + # STDPKG narrows Deno's test discovery to this package's corpus. + - name: Test + shell: bash + working-directory: std + env: + STDPKG: ${{ inputs.package }} + run: deno test --allow-all tests/ + + # Stage native -> .wasm, pure-Python -> .py. + - name: Stage artifact + shell: bash + run: | + mkdir -p _dist + if [ -f "std/${{ inputs.package }}/src/entry.py" ]; then + cp "std/${{ inputs.package }}/src/entry.py" "_dist/${{ inputs.package }}.py" + else + cp "std/${{ inputs.package }}/target/wasm32-unknown-unknown/release/${{ inputs.package }}.wasm" "_dist/${{ inputs.package }}.wasm" + fi + + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: dist-${{ inputs.package }} + path: _dist/ + retention-days: 1 diff --git a/.github/workflows/README.md b/.github/workflows/README.md deleted file mode 100644 index 25589758..00000000 --- a/.github/workflows/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Edge Python CI/CD - -``` -deno lint -> deno test ┐ -check -> wasm -> runtime -> demo -``` - -| Workflow | Role | -|----------|------| -| `_check.yml` | `cargo shear` + `clippy` (host and wasm targets) | -| `_wasm.yml` | Builds and optimizes `compiler_lib.wasm`. On tags, attaches the `.wasm` to the GitHub Release | -| `_runtime_check.yml` | JS-side gate: `deno lint runtime/` + `deno test runtime/tests/` (Playwright + Chromium driving `createWorker` against the CDN-deployed wasm). Independent branch, runs in parallel with the Rust pipeline; only the CDN upload below blocks on it | -| `_runtime.yml` | Bundles `runtime/` + `compiler_lib.wasm` and deploys them to Cloudflare Pages | -| `_demo.yml` | Hashes `compiler_lib.wasm` into `version.json` (cache-busting) and deploys `demo/` to Cloudflare Pages | -| `cli.yml` | Standalone (not part of the pipeline above): builds and tests `cli/`; on `main` pushes also publishes the release binary + `cli/setup/` scripts (`install.sh`, `uninstall.sh`) to GitHub Pages | -| `host.yml` | Standalone: deno-lints and tests each host capability (`dom`, `network`, `storage`, `time`) in headless Chromium; on `main` pushes also deploys their ESM sources to Cloudflare Pages (`edge-python-host`) | -| `std.yml` | Standalone: clippy + build + optimize + test each stdpkg (`json`, `re`, `math` as wasm; `test` is pure Edge Python, so its steps skip the wasm build and only run the corpus); on `main` pushes also deploys the per-package `.wasm` to Cloudflare Pages (`edge-python-std`) | -| `docs.yml` | Standalone (triggered only by `docs/**` changes): `npm ci` + `next build` static export of the Nextra docs (`docs/out`, sitemap via `postbuild`); PRs build only, `main` pushes also deploy to Cloudflare Pages (`edge-python-docs`) | - -## Cloudflare Pages - -Five **Direct Upload** projects, Actions pushes prebuilt directories via `wrangler pages deploy`; Cloudflare doesn't clone or build. - -| Project | Source | Production URL | -|---------|--------|----------------| -| `edge-python-demo` | `demo/` (wasm hashed for `version.json`, not bundled) | `https://edge-python-demo.pages.dev` | -| `edge-python-runtime` | `runtime/` + bundled `compiler_lib.wasm` | `https://edge-python-runtime.pages.dev` | -| `edge-python-host` | `host//src/` for each capability, flattened to `/` | `https://edge-python-host.pages.dev` | -| `edge-python-std` | per-package optimized `.wasm` from `std//` | `https://edge-python-std.pages.dev` | -| `edge-python-docs` | `docs/out` (Nextra static export) | `https://edgepython.com` (custom domain; also `https://edge-python-docs.pages.dev`) | - -All five deploys run **only on pushes to `main`** and are pinned to the production `main` branch in the matching workflow (`_runtime.yml` / `_demo.yml` / `host.yml` / `std.yml` / `docs.yml`). PRs and tags never deploy; the next `main` push refreshes the projects. - -### Cloudflare and GitHub setup - -```bash -# Wrangler CLI (Node 22+) -npx wrangler login -npx wrangler pages project create edge-python-demo --production-branch=main -npx wrangler pages project create edge-python-runtime --production-branch=main -npx wrangler pages project create edge-python-docs --production-branch=main -``` - -`edge-python-docs` serves `edgepython.com` (replacing the old Mintlify docs): after the first deploy, add `edgepython.com` as a custom domain on the project (Pages -> Custom domains) and remove it from Mintlify. - -Repo secrets (*Settings -> Secrets and variables -> Actions*): - -- `CLOUDFLARE_API_TOKEN`, `Account -> Cloudflare Pages -> Edit`. Create via dashboard: . -- `CLOUDFLARE_ACCOUNT_ID`, from `npx wrangler whoami` or any dashboard sidebar. - -Rotate: create new token -> update secret -> revoke old token. - -## Releases - -Pushing a `v*` tag triggers the pipeline; `_wasm.yml` uploads `compiler_lib.wasm` to the matching Release. Tag must match workspace version. - -1. Bump `version` under `[workspace.package]` in root `Cargo.toml` (every crate inherits via `version.workspace = true`). Run `cargo check` to refresh `Cargo.lock`, commit. -2. Tag and push: - -```bash -git tag v0.1.0 -git push origin v0.1.0 -``` - -On tag push: `_check` lints, `_wasm` builds and optimizes the artifact and attaches it to a fresh Release with auto-generated notes. The CDN deploys (`_runtime` + `_demo`) do not run on tags; they already deployed from the preceding `main` push. - -Nothing is published to crates.io, distribution is the `.wasm` on the Release. `starter-module` carries its own version and isn't bumped with the workspace. - -Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares `links = "compiler_lib"` and `compiler/build.rs` downloads `/releases/download/v/compiler_lib.wasm` into `OUT_DIR`. Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see [root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`. - -Gated behind the default-on `prebuilt` feature. Producer-side steps (`_check`, `_wasm`) pass `--no-default-features` to avoid fetching the asset that this same pipeline uploads later. diff --git a/.github/workflows/_check.yml b/.github/workflows/_check.yml deleted file mode 100644 index 0e210c8e..00000000 --- a/.github/workflows/_check.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: _Check - -on: - workflow_call: - -jobs: - lint: - name: Clippy & Shear - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - - name: Install cargo-shear - run: cargo install cargo-shear - - # Detects declared but unused dependencies across the whole workspace. - - name: Shear (unused deps) - run: cargo shear - - # Host clippy. Wasm crates excluded via default-members. `--no-default-features` disables the producer's `prebuilt` feature so `compiler/build.rs` doesn't try to download the release wasm asset (which doesn't exist yet on the first publish of a new tag). - - name: Clippy (host / tests) - run: cargo clippy --all-targets --no-default-features -- -D warnings - - # Lint for wasm target. - - name: Install wasm target - run: rustup target add wasm32-unknown-unknown - - - name: Clippy (wasm) - run: cargo clippy --lib --target wasm32-unknown-unknown -p edge-python -p slugify-mod -- -D warnings diff --git a/.github/workflows/_demo.yml b/.github/workflows/_demo.yml deleted file mode 100644 index ff9fbe2a..00000000 --- a/.github/workflows/_demo.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: _Demo Deploy - -on: - workflow_call: - -jobs: - deploy: - name: Cloudflare Pages - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: "22" - - # Download the .wasm for hashing only; the demo loads it from the CDN at runtime (https://runtime.edgepython.com/js/compiler_lib.wasm), so it does not ship inside demo/. - - name: Download wasm artifact - uses: actions/download-artifact@v8 - with: - name: compiler_lib_wasm - path: /tmp/wasm/ - - # Hash all worker dependencies: the compiler_lib.wasm binary, runtime JS sources from edge-python-runtime, and Python entry files from edge-python-demo - - name: Write version manifest - run: | - HASH=$( { sha256sum /tmp/wasm/compiler_lib.wasm; \ - find runtime -type f | LC_ALL=C sort | xargs sha256sum; \ - find demo/runtime -type f | LC_ALL=C sort | xargs sha256sum; \ - } | sha256sum | cut -c1-12) - printf '{"v":"%s"}\n' "$HASH" > demo/version.json - - - name: Build Tailwind CSS - working-directory: demo - run: | - echo "@tailwind base;@tailwind components;@tailwind utilities;" \ - | npx tailwindcss@3 --input - --output tailwind.css --minify - - - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v4 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - # Pin to the Pages production branch. See the matching comment in _runtime.yml. - command: pages deploy demo --project-name=edge-python-demo --branch=main diff --git a/.github/workflows/_runtime.yml b/.github/workflows/_runtime.yml deleted file mode 100644 index 2af985c3..00000000 --- a/.github/workflows/_runtime.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: _Runtime CDN Upload - -on: - workflow_call: - -jobs: - deploy: - name: Cloudflare Pages - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: "22" - - # Download the .wasm already compiled and optimized by _wasm.yml. - - name: Download wasm artifact - uses: actions/download-artifact@v8 - with: - name: compiler_lib_wasm - path: runtime/ - - # Stage runtime/ under /js/ so Pages serves it at runtime.edgepython.com/js/*. Reserved siblings: /wasi/ for the future WASI runtime. - - name: Stage publish dir - run: | - mkdir -p _site - cp -r runtime _site/js - - - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v4 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - # Pin to the Pages production branch. The job only runs on main pushes (gated in pipeline.yml). - command: pages deploy _site --project-name=edge-python-runtime --branch=main diff --git a/.github/workflows/_runtime_check.yml b/.github/workflows/_runtime_check.yml deleted file mode 100644 index 4824f8c9..00000000 --- a/.github/workflows/_runtime_check.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: _Runtime Check - -on: - workflow_call: - -jobs: - lint: - name: Deno Lint - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Cache Deno modules - uses: actions/cache@v5 - with: - path: ~/.cache/deno - key: deno-${{ runner.os }}-${{ hashFiles('**/deno.json', '**/deno.lock') }} - restore-keys: deno-${{ runner.os }}- - - - name: Lint runtime/ - run: deno lint runtime/ - - test: - name: Deno Test (Playwright + CDN wasm) - needs: lint - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Cache Deno modules - uses: actions/cache@v5 - with: - path: ~/.cache/deno - key: deno-${{ runner.os }}-${{ hashFiles('**/deno.json', '**/deno.lock') }} - restore-keys: deno-${{ runner.os }}- - - # ~150MB Chromium binary; cached across runs so only the first run pays the download. - - name: Cache Playwright browsers - uses: actions/cache@v5 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-chromium - - # --with-deps installs OS-level libs (libnss, libatk, ...) required by Chromium on Linux runners. - - name: Install Chromium - run: deno run -A npm:playwright install --with-deps chromium - - - name: Test runtime/tests/ - run: deno test --allow-all runtime/tests/runtime.test.js diff --git a/.github/workflows/_wasm.yml b/.github/workflows/_wasm.yml deleted file mode 100644 index 10cf5f1b..00000000 --- a/.github/workflows/_wasm.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: _WebAssembly Build - -on: - workflow_call: - -jobs: - build: - name: WebAssembly - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v6 - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src - - - name: Install wasm target - run: rustup target add wasm32-unknown-unknown - - - name: Install wasm-opt - run: | - curl -sSL https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-linux.tar.gz \ - | tar -xz --strip-components=2 -C /usr/local/bin binaryen-version_121/bin/wasm-opt - wasm-opt --version - - - name: Build - run: | - RUSTFLAGS="-Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort" \ - cargo +nightly build \ - --target wasm32-unknown-unknown \ - --lib \ - --release \ - -p edge-python \ - -Z build-std=std,panic_abort - - - name: Size (unoptimized) - run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm - - - name: Optimize - run: | - INPUT=target/wasm32-unknown-unknown/release/compiler_lib.wasm - - wasm-opt -Oz --converge \ - --generate-global-effects \ - --strip-debug --strip-producers \ - --enable-bulk-memory-opt \ - --enable-nontrapping-float-to-int \ - --enable-sign-ext \ - -tnh \ - -o /tmp/wasm_stage1.wasm "$INPUT" - - wasm-opt --flatten --rereloop -Oz -Oz \ - --enable-bulk-memory-opt \ - --enable-nontrapping-float-to-int \ - --enable-sign-ext \ - -o "$INPUT" /tmp/wasm_stage1.wasm - - rm /tmp/wasm_stage1.wasm - - - name: Size (optimized) - run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm - - # `--no-default-features` disables the producer's `prebuilt` feature so `compiler/build.rs` doesn't try to fetch the release wasm asset (which is uploaded later in this job on a first-tag publish). - - name: Test - run: cargo test -p edge-python --no-default-features - - - name: Upload wasm artifact - uses: actions/upload-artifact@v6 - with: - name: compiler_lib_wasm - path: target/wasm32-unknown-unknown/release/compiler_lib.wasm - retention-days: 1 - - - name: Upload WASM Release - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: target/wasm32-unknown-unknown/release/compiler_lib.wasm - fail_on_unmatched_files: true - generate_release_notes: true diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml deleted file mode 100644 index b5a94fa1..00000000 --- a/.github/workflows/cli.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: CLI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - build_test: - name: Build & Test - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@stable - - # `cli/` is a separate workspace; key the cache off its Cargo.lock and target dir. - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: cli -> cli/target - - # Engine tests drive a real Chromium; ubuntu-latest ships google-chrome-stable preinstalled. - - name: Build - working-directory: cli - run: cargo build - - - name: Test - working-directory: cli - run: cargo test - - # One native build per target; GitHub's free ARM/macOS runners avoid the cross-compile pain. - build_release: - name: Build release (${{ matrix.target }}) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: build_test - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-musl - runner: ubuntu-latest - - target: aarch64-unknown-linux-musl - runner: ubuntu-24.04-arm - - target: x86_64-apple-darwin - runner: macos-latest - - target: aarch64-apple-darwin - runner: macos-latest - runs-on: ${{ matrix.runner }} - - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Install musl tools - if: contains(matrix.target, 'linux-musl') - run: sudo apt-get update && sudo apt-get install -y musl-tools - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: cli -> cli/target - key: ${{ matrix.target }} - - - name: Build release - working-directory: cli - run: cargo build --release --target ${{ matrix.target }} - - - name: Tar - working-directory: cli - run: tar -C target/${{ matrix.target }}/release -czf edge-${{ matrix.target }}.tar.gz edge - - - uses: actions/upload-artifact@v4 - with: - name: edge-${{ matrix.target }} - path: cli/edge-${{ matrix.target }}.tar.gz - - pages: - name: Publish to GitHub Pages - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: build_release - runs-on: ubuntu-latest - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} - - steps: - - uses: actions/checkout@v6 - - - name: Collect tarballs and stage Pages site - run: | - mkdir -p _site - cp cli/setup/*.sh _site/ - - - uses: actions/download-artifact@v4 - with: - path: _site - pattern: edge-* - merge-multiple: true - - - uses: actions/upload-pages-artifact@v3 - with: - path: _site - - - id: deploy - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 217598f9..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Docs - -on: - push: - branches: [main] - paths: ["docs/**", ".github/workflows/docs.yml"] - pull_request: - branches: [main] - paths: ["docs/**", ".github/workflows/docs.yml"] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - # Build the docs static export and deploy it to Cloudflare Pages. - deploy: - name: Cloudflare Pages - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: "22" - cache: npm - cache-dependency-path: docs/package-lock.json - - # Reproducible install from the committed lockfile. - - name: Install - working-directory: docs - run: npm ci - - # `output: 'export'` makes `next build` emit a fully static site to docs/out. - - name: Build - working-directory: docs - run: npm run build - - # Deploy only on main pushes; PRs stop after a successful build. - - name: Deploy to Cloudflare Pages - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: cloudflare/wrangler-action@v4 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - # Pin to the Pages production branch (job is already gated to main). - command: pages deploy docs/out --project-name=edge-python-docs --branch=main diff --git a/.github/workflows/host.yml b/.github/workflows/host.yml deleted file mode 100644 index b049c554..00000000 --- a/.github/workflows/host.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Host - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - # Deno lint per capability. Anchors the matrix so downstream jobs reuse it. - lint: - name: Lint (${{ matrix.capability }}) - runs-on: ubuntu-latest - strategy: &capability-matrix - fail-fast: false - matrix: - capability: [dom, network, storage, time] - steps: - - uses: actions/checkout@v6 - - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - # Stays consistent with the test job's cache; protects host packages that lint remote-imported modules. - - name: Cache Deno modules - uses: actions/cache@v5 - with: - path: ~/.cache/deno - key: deno-${{ runner.os }}-${{ hashFiles('host/**/deno.json', 'host/**/deno.lock') }} - restore-keys: deno-${{ runner.os }}- - - - name: Lint src/ - working-directory: host/${{ matrix.capability }} - run: deno lint src/ - - # Smoke test each capability against the real demo in headless Chromium. Gated on lint to avoid wasting Chromium time. - test: - name: Test (${{ matrix.capability }}) - needs: lint - runs-on: ubuntu-latest - strategy: *capability-matrix - steps: - - uses: actions/checkout@v6 - - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Cache Deno modules - uses: actions/cache@v5 - with: - path: ~/.cache/deno - key: deno-${{ runner.os }}-${{ hashFiles('host/**/deno.json', 'host/**/deno.lock') }} - restore-keys: deno-${{ runner.os }}- - - # ~150MB Chromium; cached across runs so only the first run pays the download. - - name: Cache Playwright browsers - uses: actions/cache@v5 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-chromium - - # --with-deps installs OS-level libs Chromium needs. Idempotent: cache-hit makes it a no-op. - - name: Install Chromium - run: deno run -A npm:playwright install --with-deps chromium - - # `HOSTCAP` narrows test discovery to this capability's corpus. - - name: Test tests/ - working-directory: host - env: - HOSTCAP: ${{ matrix.capability }} - run: deno test --allow-all tests/ - - # Publish every capability's ESM to Cloudflare Pages on pushes to main only. - deploy: - name: Deploy - needs: test - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - # Host serves its ESM sources, so checkout the tree. - - uses: actions/checkout@v6 - - # Flatten each capability's src/ into _site/ so the public path is /index.js; siblings move together, so relative imports stay valid. - - name: Assemble site - run: | - mkdir -p _site - for dir in host/*/src; do - cap="$(basename "$(dirname "$dir")")" - mkdir -p "_site/$cap" - cp -r "$dir"/. "_site/$cap/" - done - - # Pages serves the modules at host.edgepython.com//*. - - name: Deploy - uses: cloudflare/wrangler-action@v4 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy _site --project-name=edge-python-host --branch=main diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..35d963c7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,165 @@ +name: CI / CD + +# Single pipeline for the whole monorepo. Each job's logic lives in a composite +# action under .github/actions/; this file only wires the dependency graph. +# +# compiler-check ┐ +# runtime-lint ─┴─► compiler ─► runtime ─┬─► host (matrix) ─┐ +# └─► std (matrix) ─┤ +# cli-release (matrix, starts at t=0) ─────────────────────┴─► cli-test +# │ (push to main) +# cdn ◄── demo ◄── docs ◄────────┘ +# docs deploys only on push to main, gated behind the full pipeline. + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + compiler-check: + name: Compiler / Lint and Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/compiler + with: + mode: check + + runtime-lint: + name: Runtime / Deno Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/runtime + with: + mode: lint + + compiler: + name: Compiler / Test and Release + needs: [compiler-check, runtime-lint] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/compiler + with: + mode: build + github-token: ${{ github.token }} + + runtime: + name: Runtime / Deno Test + needs: [compiler] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/runtime + with: + mode: test + + host: + name: Host / ${{ matrix.capability }} + needs: [runtime] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + capability: [dom, network, storage, time] + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/host + with: + capability: ${{ matrix.capability }} + + std: + name: Std / ${{ matrix.package }} + needs: [runtime] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: [json, re, math, test] + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/std + with: + package: ${{ matrix.package }} + + # Starts at t=0 (no needs): the heavy release build runs in parallel; cli-test awaits its artifacts. + cli-release: + name: CLI / Release (${{ matrix.target }}) + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-musl + runner: ubuntu-latest + - target: aarch64-unknown-linux-musl + runner: ubuntu-24.04-arm + - target: x86_64-apple-darwin + runner: macos-latest + - target: aarch64-apple-darwin + runner: macos-latest + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/cli + with: + mode: release + target: ${{ matrix.target }} + + cli-test: + name: CLI / Test + needs: [host, std, cli-release] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/cli + with: + mode: test + + cdn: + name: Cloudflare Upload (CDN) + needs: [cli-test] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/cdn-deploy + with: + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + demo: + name: Demo + needs: [cli-test] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/demo + with: + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + docs: + name: Docs / Build + needs: [cli-test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/docs + with: + deploy: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml deleted file mode 100644 index 53b21927..00000000 --- a/.github/workflows/pipeline.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: CI / CD - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] - -jobs: - # Lint and check the codebase, including running clippy and checking for unused code. - check: - name: Lint and Check - uses: ./.github/workflows/_check.yml - secrets: inherit - - # Build the release WebAssembly artifact. - wasm: - name: WebAssembly - needs: check - permissions: - contents: write - uses: ./.github/workflows/_wasm.yml - secrets: inherit - - # JS-side gate: lint runtime/ and run the Deno + Playwright suite against the CDN-deployed wasm. Independent of the Rust pipeline above, runs in parallel with `wasm`, converges at `runtime` below. - runtime_check: - name: Runtime JS Check - uses: ./.github/workflows/_runtime_check.yml - secrets: inherit - - # Upload runtime (JS + WASM) to the CDN. Convergence point: blocked on both `wasm` (artifact) and `runtime_check` (JS gate). - runtime: - name: Upload Runtime to CDN - needs: [wasm, runtime_check] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - permissions: - contents: read - uses: ./.github/workflows/_runtime.yml - secrets: inherit - - # Build the demo after the runtime upload, since the demo consumes the CDN-hosted runtime. - demo: - name: Build Demo - needs: runtime - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - permissions: - contents: read - uses: ./.github/workflows/_demo.yml - secrets: inherit diff --git a/.github/workflows/std.yml b/.github/workflows/std.yml deleted file mode 100644 index aab1bf96..00000000 --- a/.github/workflows/std.yml +++ /dev/null @@ -1,196 +0,0 @@ -name: Std - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - BINARYEN_VERSION: "121" - -jobs: - # Clippy each stdpkg on wasm32. Anchors the matrix so downstream jobs reuse it. - lint: - name: Lint (${{ matrix.package }}) - runs-on: ubuntu-latest - strategy: &package-matrix - fail-fast: false - matrix: - package: [json, re, math, test] - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-unknown-unknown - components: clippy - - # Stable prefix avoids the nightly job's cache. Keyed per package so each shard owns its slot. - - name: Cache Cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - std/${{ matrix.package }}/target - key: cargo-stable-${{ runner.os }}-${{ matrix.package }}-${{ hashFiles(format('std/{0}/Cargo.toml', matrix.package)) }} - restore-keys: cargo-stable-${{ runner.os }}-${{ matrix.package }}- - - # Lint only the cdylib; --all-targets clashes with its panic handler. - - name: Clippy src/ - if: matrix.package != 'test' - working-directory: std/${{ matrix.package }} - run: cargo clippy --release --target wasm32-unknown-unknown -- -D warnings - - # Build, optimize, and test each package; uploads the wasm artifact for the deploy step. - wasm: - name: WASM (${{ matrix.package }}) - needs: lint - runs-on: ubuntu-latest - strategy: *package-matrix - env: - WASM: std/${{ matrix.package }}/target/wasm32-unknown-unknown/release/${{ matrix.package }}.wasm - steps: - - uses: actions/checkout@v6 - - # build-std needs nightly plus rust-src. - - uses: dtolnay/rust-toolchain@nightly - with: - targets: wasm32-unknown-unknown - components: rust-src - - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - # Nightly prefix avoids the lint job's cache. - - name: Cache Cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - std/${{ matrix.package }}/target - key: cargo-nightly-${{ runner.os }}-${{ matrix.package }}-${{ hashFiles(format('std/{0}/Cargo.toml', matrix.package)) }} - restore-keys: cargo-nightly-${{ runner.os }}-${{ matrix.package }}- - - - name: Cache Deno modules - uses: actions/cache@v5 - with: - path: ~/.cache/deno - key: deno-${{ runner.os }}-${{ hashFiles('std/**/deno.json', 'std/**/deno.lock') }} - restore-keys: deno-${{ runner.os }}- - - - name: Cache Playwright browsers - uses: actions/cache@v5 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-chromium - - - name: Install Chromium - run: deno run -A npm:playwright install --with-deps chromium - - # apt ships an old binaryen, so fetch the upstream release. - - name: Install wasm-opt - if: matrix.package != 'test' - run: | - curl -sSL "https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/binaryen-version_${BINARYEN_VERSION}-x86_64-linux.tar.gz" \ - | tar -xz --strip-components=2 -C /usr/local/bin "binaryen-version_${BINARYEN_VERSION}/bin/wasm-opt" - wasm-opt --version - - # `test` is pure Edge Python (src/entry.py): no crate to compile, so skip the wasm build/optimize/upload and let the corpus run below. - - name: Build - if: matrix.package != 'test' - working-directory: std/${{ matrix.package }} - run: | - RUSTFLAGS="-Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort" \ - cargo +nightly build \ - --target wasm32-unknown-unknown \ - --lib --release \ - -Z build-std=std,panic_abort - - - name: Size (unoptimized) - if: matrix.package != 'test' - run: ls -lh "$WASM" - - # Two passes: -Oz with traps-never-happen, then reflatten for a fresh CFG. - - name: Optimize - if: matrix.package != 'test' - run: | - wasm-opt -Oz --converge \ - --generate-global-effects \ - --strip-debug --strip-producers \ - --enable-bulk-memory-opt \ - --enable-nontrapping-float-to-int \ - --enable-sign-ext \ - -tnh \ - -o /tmp/wasm_stage1.wasm "$WASM" - - wasm-opt --flatten --rereloop -Oz -Oz \ - --enable-bulk-memory-opt \ - --enable-nontrapping-float-to-int \ - --enable-sign-ext \ - -o "$WASM" /tmp/wasm_stage1.wasm - - rm /tmp/wasm_stage1.wasm - - - name: Size (optimized) - if: matrix.package != 'test' - run: ls -lh "$WASM" - - # STDPKG narrows Deno's test discovery to this package's corpus. The driver routes .py packages to src/entry.py, native ones to the built wasm. - - name: Test - working-directory: std - env: - STDPKG: ${{ matrix.package }} - run: deno test --allow-all tests/ - - # Stage the deployable under its canonical CDN name: native -> .wasm, pure-Python (src/entry.py) -> .py. - - name: Stage artifact - run: | - mkdir -p _dist - if [ -f "std/${{ matrix.package }}/src/entry.py" ]; then - cp "std/${{ matrix.package }}/src/entry.py" "_dist/${{ matrix.package }}.py" - else - cp "$WASM" "_dist/${{ matrix.package }}.wasm" - fi - - - uses: actions/upload-artifact@v6 - with: - name: dist-${{ matrix.package }} - path: _dist/ - retention-days: 1 - - # Publish every per-package artifact (.wasm + .py) to Cloudflare Pages on pushes to main only. - deploy: - name: Deploy - needs: wasm - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - # Pull every per-package artifact uploaded by the matrix above. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - pattern: dist-* - path: _site/ - merge-multiple: true - - # Pages serves each package at std.edgepython.com/* (e.g. json.wasm, test.py). - - name: Deploy - uses: cloudflare/wrangler-action@v4 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy _site --project-name=edge-python-std --branch=main From afc446fb2df5e633f571a9b6e9e40486678a3152 Mon Sep 17 00:00:00 2001 From: dylan-sutton-chavez Date: Sun, 31 May 2026 01:33:38 -0600 Subject: [PATCH 2/2] ci: Clean documentation, and refactor workflow. --- .github/README.md | 54 ++++++++------------------------- .github/actions/cli/action.yml | 34 +++++++++++++-------- .github/actions/docs/action.yml | 34 ++++++++++++++++----- .github/workflows/main.yml | 43 +++++++++++++++++--------- cli/src/uninstall.rs | 5 ++- 5 files changed, 91 insertions(+), 79 deletions(-) diff --git a/.github/README.md b/.github/README.md index 93cb0355..cc6d332b 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,27 +1,8 @@ # Edge Python CI/CD -One workflow, [`main.yml`](main.yml), drives the whole monorepo, so the Actions tab -shows a single "CI / CD" run per push/PR with every job as a node in one graph. Each -package's logic lives in a **composite action** under [`../actions/`](../actions); -`main.yml` only wires the dependency graph. The composite actions are not workflows -and do not appear in the Actions tab. +One workflow, [`main.yml`](workflows/main.yml), drives the whole monorepo, so the Actions tab shows a single "CI / CD" run per push/PR with every job as a node in one graph. Each package's logic lives in a **composite action** under [`actions/`](actions); `main.yml` only wires the dependency graph. The composite actions are not workflows and do not appear in the Actions tab. -``` -compiler-check ┐ -runtime-lint ─┴─► compiler ─► runtime ─┬─► host (matrix) ─┐ - └─► std (matrix) ─┤ -cli-release (matrix, starts at t=0) ───────────────────────┴─► cli-test - │ (push to main) - cdn ◄── demo ◄── docs ◄───────────┘ - (docs builds on every run; deploys only on push to main) -``` - -`compiler-check`, `runtime-lint` and `cli-release` start at t=0. If any job fails the -dependents never run (`needs:`), so a red build stops the deploys, including the docs -deploy. The -`host` and `std` matrices use `fail-fast: false` so one capability / package failure -still reports the others. `cli-release` is the slow heavy build, so it starts -immediately and `cli-test` waits on `host`, `std`, and the release artifacts. +`compiler-check`, `runtime-lint` and `cli-lint` start at t=0. If any job fails the dependents never run (`needs:`), so a red build stops the deploys. `docs-build` runs on every event so a PR that breaks the docs is caught early; the deploy chain (`cdn → demo → docs-deploy`) is gated to `main` and pinned to the production branch. The `host` and `std` matrices use `fail-fast: false` so one capability / package failure still reports the others. `cli-lint` runs clippy + check once, then the heavy per-target `cli-release` build runs; `cli-test` waits on `host`, `std`, and the release artifacts. ## Composite actions @@ -31,15 +12,14 @@ immediately and `cli-test` waits on `host`, `std`, and the release artifacts. | `runtime` | `mode: lint\|test` | lint: `deno lint runtime/`. test: Deno + Playwright suite (Chromium driving `createWorker` against the CDN wasm) | | `host` | `capability` | Deno-lints and smoke-tests one capability (`dom`, `network`, `storage`, `time`) in headless Chromium. All JS, no release | | `std` | `package` | Clippy + build + optimize + corpus test for one stdpkg (`json`, `re`, `math` as wasm; `test` is pure Edge Python, so it skips the wasm build and only runs the corpus). Stages `.wasm` / `.py`. No release | -| `cli` | `mode: release\|test`, `target` | release: lint/check (once) + `cargo build --release` per target → tarball artifact. test: `cargo test` (drives a real Chromium) | +| `cli` | `mode: lint\|release\|test`, `target` | lint: `cargo clippy -D warnings` + `cargo check` (once). release: `cargo build --release` per target → tarball artifact. test: `cargo test` (drives a real Chromium) | | `demo` | CF token + account | Hashes deps into `version.json` (cache-busting), builds Tailwind, deploys `demo/` to `edge-python-demo` | -| `docs` | `deploy`, CF token + account | `npm ci` + `next build` static export (`docs/out`, sitemap via `postbuild`). Deploys to `edge-python-docs` only when `deploy=true` | +| `docs` | `mode: build\|deploy`, CF token + account | build: `npm ci` + `next build` static export (`docs/out`, sitemap via `postbuild`), upload artifact. deploy: pull artifact + push to `edge-python-docs` | | `cdn-deploy` | CF token + account | Pulls every artifact, stages `./compiler ./runtime ./std ./host ./cli`, one `wrangler pages deploy` to `edge-python-cdn` | ## Cloudflare Pages -Three **Direct Upload** projects. Actions push prebuilt directories via -`wrangler pages deploy`; Cloudflare doesn't clone or build. +Three **Direct Upload** projects. Actions push prebuilt directories via `wrangler pages deploy`; Cloudflare doesn't clone or build. | Project | Source | Production URL | |---------|--------|----------------| @@ -47,15 +27,14 @@ Three **Direct Upload** projects. Actions push prebuilt directories via | `edge-python-demo` | `demo/` (wasm hashed for `version.json`, not bundled) | `https://edge-python-demo.pages.dev` | | `edge-python-docs` | `docs/out` (Nextra static export) | `https://edgepython.com` (custom domain; also `https://edge-python-docs.pages.dev`) | -All deploys run **only on pushes to `main`** and are pinned to the production `main` -branch. PRs and tags never deploy; the next `main` push refreshes the projects. +All deploys run **only on pushes to `main`** and are pinned to the production `main` branch. PRs and tags never deploy; the next `main` push refreshes the projects. ### Cloudflare and GitHub setup ```bash # Wrangler CLI (Node 22+) npx wrangler login -npx wrangler pages project create edge-python-cdn --production-branch=main +npx wrangler pages project create edge-python-cdn --production-branch=main npx wrangler pages project create edge-python-demo --production-branch=main npx wrangler pages project create edge-python-docs --production-branch=main ``` @@ -73,8 +52,7 @@ Rotate: create new token -> update secret -> revoke old token. ## Releases -Pushing a `v*` tag runs the pipeline; the `compiler` build job uploads -`compiler_lib.wasm` to the matching Release. Tag must match workspace version. +Pushing a `v*` tag runs the pipeline; the `compiler` build job uploads `compiler_lib.wasm` to the matching Release. Tag must match workspace version. 1. Bump `version` under `[workspace.package]` in root `Cargo.toml` (every crate inherits via `version.workspace = true`). Run `cargo check` to refresh `Cargo.lock`, commit. 2. Tag and push: @@ -84,18 +62,10 @@ git tag v0.1.0 git push origin v0.1.0 ``` -On tag push: `compiler-check` lints, then the `compiler` build job optimizes the -artifact and attaches it to a fresh Release with auto-generated notes. The CDN, demo -and docs deploys do not run on tags; they already deployed from the preceding `main` push. +On tag push: `compiler-check` lints, then the `compiler` build job optimizes the artifact and attaches it to a fresh Release with auto-generated notes. The CDN, demo and docs deploys do not run on tags; they already deployed from the preceding `main` push. -Nothing is published to crates.io, distribution is the `.wasm` on the Release. -`starter-module` carries its own version and isn't bumped with the workspace. +Nothing is published to crates.io, distribution is the `.wasm` on the Release. `starter-module` carries its own version and isn't bumped with the workspace. -Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares -`links = "compiler_lib"` and `compiler/build.rs` downloads -`/releases/download/v/compiler_lib.wasm` into `OUT_DIR`. -Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see -[root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`. +Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares `links = "compiler_lib"` and `compiler/build.rs` downloads `/releases/download/v/compiler_lib.wasm` into `OUT_DIR`. Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see [root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`. -Gated behind the default-on `prebuilt` feature. Producer-side compiler steps pass -`--no-default-features` to avoid fetching the asset that this same pipeline uploads later. +Gated behind the default-on `prebuilt` feature. Producer-side compiler steps pass `--no-default-features` to avoid fetching the asset that this same pipeline uploads later. diff --git a/.github/actions/cli/action.yml b/.github/actions/cli/action.yml index ff5c5dab..65b8b892 100644 --- a/.github/actions/cli/action.yml +++ b/.github/actions/cli/action.yml @@ -1,9 +1,9 @@ name: CLI -description: cli/ is its own workspace. release = lint/check + build per target; test = cargo test. +description: cli/ is its own workspace. lint = clippy + check; release = build per target; test = cargo test. inputs: mode: - description: "release | test" + description: "lint | release | test" required: true target: description: Rust target triple (release mode). @@ -13,12 +13,31 @@ inputs: runs: using: composite steps: + - name: Toolchain (lint) + if: inputs.mode == 'lint' + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Cache Rust (lint) + if: inputs.mode == 'lint' + uses: Swatinem/rust-cache@v2 + with: + workspaces: cli -> cli/target + + - name: Clippy and check + if: inputs.mode == 'lint' + shell: bash + working-directory: cli + run: | + cargo clippy --all-targets -- -D warnings + cargo check + - name: Toolchain (release) if: inputs.mode == 'release' uses: dtolnay/rust-toolchain@stable with: targets: ${{ inputs.target }} - components: clippy - name: Install musl tools if: inputs.mode == 'release' && contains(inputs.target, 'linux-musl') @@ -32,15 +51,6 @@ runs: workspaces: cli -> cli/target key: ${{ inputs.target }} - # Lint + check once, anchored to the linux x86 target so the matrix doesn't repeat it. - - name: Clippy and check - if: inputs.mode == 'release' && inputs.target == 'x86_64-unknown-linux-musl' - shell: bash - working-directory: cli - run: | - cargo clippy --release --target "${{ inputs.target }}" -- -D warnings - cargo check --release --target "${{ inputs.target }}" - - name: Build release if: inputs.mode == 'release' shell: bash diff --git a/.github/actions/docs/action.yml b/.github/actions/docs/action.yml index 4c73f3d1..baa690db 100644 --- a/.github/actions/docs/action.yml +++ b/.github/actions/docs/action.yml @@ -1,11 +1,10 @@ -name: Docs build & deploy -description: Build the Nextra static export (docs/out); deploy to edge-python-docs when deploy=true. +name: Docs +description: Nextra static export. build = next build + upload docs/out; deploy = pull artifact + push to edge-python-docs. inputs: - deploy: - description: true to deploy after a successful build. - required: false - default: "false" + mode: + description: "build | deploy" + required: true cloudflare-api-token: description: Cloudflare API token (deploy only). required: false @@ -18,7 +17,9 @@ inputs: runs: using: composite steps: - - uses: actions/setup-node@v6 + - name: Setup Node + if: inputs.mode == 'build' + uses: actions/setup-node@v6 with: node-version: "22" cache: npm @@ -26,17 +27,34 @@ runs: # Reproducible install from the committed lockfile. - name: Install + if: inputs.mode == 'build' shell: bash working-directory: docs run: npm ci - name: Build + if: inputs.mode == 'build' shell: bash working-directory: docs run: npm run build + - name: Upload static export + if: inputs.mode == 'build' + uses: actions/upload-artifact@v6 + with: + name: docs-out + path: docs/out + retention-days: 1 + + - name: Download static export + if: inputs.mode == 'deploy' + uses: actions/download-artifact@v8 + with: + name: docs-out + path: docs/out + - name: Deploy to Cloudflare Pages - if: inputs.deploy == 'true' + if: inputs.mode == 'deploy' uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ inputs.cloudflare-api-token }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35d963c7..a861fdb0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,15 +1,6 @@ name: CI / CD -# Single pipeline for the whole monorepo. Each job's logic lives in a composite -# action under .github/actions/; this file only wires the dependency graph. -# -# compiler-check ┐ -# runtime-lint ─┴─► compiler ─► runtime ─┬─► host (matrix) ─┐ -# └─► std (matrix) ─┤ -# cli-release (matrix, starts at t=0) ─────────────────────┴─► cli-test -# │ (push to main) -# cdn ◄── demo ◄── docs ◄────────┘ -# docs deploys only on push to main, gated behind the full pipeline. +# Single pipeline for the whole monorepo. Each job's logic lives in a composite action under .github/actions/; this file only wires the dependency graph. on: push: @@ -95,9 +86,20 @@ jobs: with: package: ${{ matrix.package }} - # Starts at t=0 (no needs): the heavy release build runs in parallel; cli-test awaits its artifacts. + # Clippy + check once (no matrix); gates the release build. + cli-lint: + name: CLI / Lint and Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/cli + with: + mode: lint + + # Heavy release build per target; starts as soon as cli-lint is green. cli-release: name: CLI / Release (${{ matrix.target }}) + needs: [cli-lint] strategy: fail-fast: false matrix: @@ -142,7 +144,7 @@ jobs: demo: name: Demo - needs: [cli-test] + needs: [cdn] if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: @@ -152,7 +154,8 @@ jobs: cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - docs: + # Builds on every run (PR gate) and uploads the static export for docs-deploy. + docs-build: name: Docs / Build needs: [cli-test] runs-on: ubuntu-latest @@ -160,6 +163,18 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/docs with: - deploy: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + mode: build + + # Tail of the cdn -> demo -> docs deploy chain; main pushes only. + docs-deploy: + name: Docs / Deploy + needs: [demo, docs-build] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/docs + with: + mode: deploy cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/cli/src/uninstall.rs b/cli/src/uninstall.rs index e18de951..26e3f329 100644 --- a/cli/src/uninstall.rs +++ b/cli/src/uninstall.rs @@ -27,11 +27,10 @@ pub fn run() -> Result<()> { // Tell the script which prompt path the user already answered. cmd.env("EDGE_UNINSTALL_REMOVE_BROWSER", if remove_browser { "1" } else { "0" }); // Point at the install dir derived from where this binary lives, so non-default installs still clean up. - if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { + if let Ok(exe) = std::env::current_exe() + && let Some(dir) = exe.parent() { cmd.env("EDGE_INSTALL_DIR", dir); } - } let status = cmd.status().map_err(|e| anyhow!("running bash: {e}"))?; let _ = std::fs::remove_file(&temp);