diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..736f24b --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,50 @@ +name: pages +on: + push: + branches: [master] + paths: + - "apps/site/**" + - "packages/core/**" + - "pnpm-lock.yaml" + - ".github/workflows/pages.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: { node-version: 22, cache: pnpm } + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @enspack/core build + # Demo fixtures until the Sepolia indexer is reachable from the public internet. + - run: pnpm --filter @enspack/site build + env: + VITE_BASE: /enspack/ + VITE_DATA_SOURCE: demo + VITE_CHAIN: sepolia + - uses: actions/configure-pages@v5 + with: { enablement: true } + - uses: actions/upload-pages-artifact@v3 + with: { path: apps/site/dist } + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/apps/site/README.md b/apps/site/README.md index c51d395..26f2706 100644 --- a/apps/site/README.md +++ b/apps/site/README.md @@ -16,6 +16,7 @@ into the browser bundle (it would pull viem and IPFS). | `VITE_REGISTRAR_URL` | `https://registrar.enspack.dev` | Registrar base URL (attestations, claim-page link) | | `VITE_CHAIN` | `sepolia` | `sepolia` \| `mainnet`; drives the testnet banner and explorer / ENS-app links | | `VITE_DATA_SOURCE` | `http` | `http` \| `demo`; `demo` serves the recorded fixtures in-app | +| `VITE_BASE` | `/` | Build-time public base path (`/enspack/` on GitHub Pages); the router basename follows it | ## Develop @@ -36,6 +37,15 @@ pnpm --filter @enspack/site build pnpm --filter @enspack/site preview ``` +## Deploy (GitHub Pages, preview) + +`.github/workflows/pages.yml` builds the site in demo mode with +`VITE_BASE=/enspack/` on every push to `master` that touches `apps/site` and +publishes it to . `pnpm build` also writes +`dist/404.html` (a copy of `index.html`, since Pages has no rewrites) and +`dist/.nojekyll`. Pages must be set to "GitHub Actions" as the source once +(Settings → Pages); the workflow attempts to enable it itself. + ## Deploy (Cloudflare Pages) Hosting target is `enspack.dev`. DNS and the Pages project are manual. diff --git a/apps/site/package.json b/apps/site/package.json index d265ad8..0e4d73e 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -9,7 +9,7 @@ }, "scripts": { "dev": "vite", - "build": "vite build", + "build": "vite build && node scripts/spa-fallback.mjs", "preview": "vite preview", "typecheck": "tsc --noEmit -p tsconfig.json", "test": "vitest run", diff --git a/apps/site/scripts/spa-fallback.mjs b/apps/site/scripts/spa-fallback.mjs new file mode 100644 index 0000000..82fd389 --- /dev/null +++ b/apps/site/scripts/spa-fallback.mjs @@ -0,0 +1,5 @@ +// GitHub Pages has no rewrite rules: serving index.html as 404.html lets deep links reach the router. +import { copyFileSync, writeFileSync } from "node:fs"; + +copyFileSync("dist/index.html", "dist/404.html"); +writeFileSync("dist/.nojekyll", ""); diff --git a/apps/site/src/lib/config.ts b/apps/site/src/lib/config.ts index 78b0a42..5cd4ab6 100644 --- a/apps/site/src/lib/config.ts +++ b/apps/site/src/lib/config.ts @@ -27,3 +27,9 @@ export function readConfig(env: ImportMetaEnv = import.meta.env): SiteConfig { } export const config: SiteConfig = readConfig(); + +/** react-router wants the base path without a trailing slash; Vite's BASE_URL always has one. */ +export function routerBasename(baseUrl: string): string { + const trimmed = baseUrl.replace(/\/+$/, ""); + return trimmed === "" ? "/" : trimmed; +} diff --git a/apps/site/src/main.tsx b/apps/site/src/main.tsx index 4d2469f..6027fd4 100644 --- a/apps/site/src/main.tsx +++ b/apps/site/src/main.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router"; import { App } from "./app.js"; import { ClientProvider } from "./lib/client-context.js"; +import { routerBasename } from "./lib/config.js"; import "./styles/tokens.css"; import "./styles/global.css"; @@ -11,7 +12,7 @@ if (root === null) throw new Error("missing #root"); createRoot(root).render( - + diff --git a/apps/site/test/lib.test.ts b/apps/site/test/lib.test.ts index 7124bb6..2074ab2 100644 --- a/apps/site/test/lib.test.ts +++ b/apps/site/test/lib.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { ApiError } from "../src/lib/api.js"; import { FixtureIndexClient, HttpIndexClient } from "../src/lib/client.js"; -import { readConfig } from "../src/lib/config.js"; +import { readConfig, routerBasename } from "../src/lib/config.js"; import { ensAppUrl, firstLabel, formatBytes, shortHex } from "../src/lib/format.js"; import { demoFixtures } from "./helpers.js"; @@ -13,6 +13,12 @@ describe("config", () => { expect(c.indexUrl).toBe("https://idx.example"); }); + it("derives the router basename from Vite's BASE_URL", () => { + expect(routerBasename("/")).toBe("/"); + expect(routerBasename("/enspack/")).toBe("/enspack"); + expect(routerBasename("/enspack")).toBe("/enspack"); + }); + it("honours mainnet + demo", () => { const c = readConfig({ VITE_CHAIN: "mainnet", VITE_DATA_SOURCE: "demo" } as ImportMetaEnv); expect(c.chain).toBe("mainnet"); diff --git a/apps/site/vite.config.ts b/apps/site/vite.config.ts index 2911915..26dfb04 100644 --- a/apps/site/vite.config.ts +++ b/apps/site/vite.config.ts @@ -1,7 +1,9 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; +// VITE_BASE lets the same bundle serve from a sub-path (GitHub Pages: "/enspack/") or the root. export default defineConfig({ + base: process.env.VITE_BASE ?? "/", plugins: [react()], build: { target: "es2022", sourcemap: true }, });