From eee9227d3abae55fa2d17d25c2431cb9da835767 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 15:46:12 +0000 Subject: [PATCH] Refresh the site: static Astro, current deps, CI, Auth0 cleanup - Update every dependency to its latest release (supersedes the open Dependabot PRs); override sharp to 0.35.5 for wrangler's miniflare. - Drop React and the Cloudflare adapter; the site builds to static files served by an assets-only Worker. - Auth0: inline the public domain and client ID, keep tokens in memory and restore the session from the same-site custom domain, handle failed logins. - CI: lint and build jobs, dependency review, Dependabot grouping. - Minimal README; setup notes in CONTRIBUTING.md and AGENTS.md. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AWtXV8CMUpAX8ktbGjgtJr --- .env.example | 6 - .gitattributes | 1 + .github/dependabot.yaml | 10 + .github/workflows/ci.yaml | 47 +- .github/workflows/dependency-review.yaml | 24 + .gitignore | 16 +- .npmrc | 2 + .nvmrc | 1 + AGENTS.md | 8 + CONTRIBUTING.md | 23 + README.md | 64 +- astro.config.mjs | 7 +- biome.json => biome.jsonc | 17 +- package-lock.json | 5343 ++++++++++------------ package.json | 36 +- public/.assetsignore | 2 - public/favicon.svg | 1 + src/env.d.ts | 12 - src/layouts/BaseLayout.astro | 7 +- src/lib/auth.ts | 81 +- src/pages/account.astro | 2 +- tsconfig.json | 10 +- wrangler.jsonc | 16 +- 23 files changed, 2587 insertions(+), 3149 deletions(-) delete mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .github/workflows/dependency-review.yaml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md rename biome.json => biome.jsonc (62%) delete mode 100644 public/.assetsignore delete mode 100644 src/env.d.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index b4214c8..0000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# Auth0 SPA configuration. -# These are PUBLIC, non-secret identifiers (they ship to the browser in the -# Authorization Code + PKCE flow). Copy this file to `.env` for local dev, and -# set the same variables in your Cloudflare build environment for deploys. -PUBLIC_AUTH0_DOMAIN=id.letsbuilda.dev -PUBLIC_AUTH0_CLIENT_ID=THoRnVJxMOfLxMAW0kaNW8nuP1q8q3qQ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 5347c62..5263e55 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -19,5 +19,15 @@ updates: javascript-dependencies: patterns: - "*" + # @astrojs/check declares a peer range of typescript ^5 || ^6, so + # TypeScript 7+ breaks `astro check`. Keep it out of the group so one + # incompatible major does not fail the whole grouped update. + exclude-patterns: + - typescript + ignore: + # @types/node follows the Node major pinned in .nvmrc (24). + - dependency-name: "@types/node" + update-types: + - version-update:semver-major cooldown: default-days: 7 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index de57cb1..7aa531c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,26 +1,61 @@ name: CI on: + pull_request: + # Docs-only changes skip CI; nothing here reads Markdown. + paths-ignore: + - "**/*.md" + merge_group: push: branches: - main - pull_request: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + # Cancel superseded PR runs only; every merge to main runs in full. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: - biome: - name: Biome + lint: runs-on: ubuntu-latest + timeout-minutes: 5 + steps: - - name: Checkout + - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Setup Biome + - name: Set up Biome uses: biomejs/setup-biome@4c91541eaada48f67d7dbd7833600ce162b68f51 # v2.7.1 - - name: Run Biome + - name: Lint run: biome ci . --reporter=github + + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npm run typecheck + + - name: Build + run: npm run build diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml new file mode 100644 index 0000000..a932ce7 --- /dev/null +++ b/.github/workflows/dependency-review.yaml @@ -0,0 +1,24 @@ +name: Dependency Review + +on: + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + dependency-review: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Review dependency changes + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.gitignore b/.gitignore index 28f22ee..da233d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,20 @@ # build output dist/ -.wrangler/ -# generated types .astro/ -worker-configuration.d.ts +.wrangler/ # dependencies node_modules/ # logs npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - # environment variables .env -.env.production +.env.* +.dev.vars* -# macOS-specific files +# editor and OS files .DS_Store - -# jetbrains setting folder .idea/ +.vscode/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..d9ca886 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +save-exact=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..248216a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.12.0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1113520 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,8 @@ +# Agent notes + +- Static Astro site, no adapter and no UI framework. Page behavior is plain ` diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 3802978..9d250fe 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -4,87 +4,69 @@ import { type User, } from "@auth0/auth0-spa-js"; -// Both values are public, non-secret identifiers for this SPA's Authorization -// Code + PKCE flow. They are inlined at build time from PUBLIC_* environment -// variables (see .env.example). -const domain = import.meta.env.PUBLIC_AUTH0_DOMAIN; -const clientId = import.meta.env.PUBLIC_AUTH0_CLIENT_ID; +// Public identifiers for this single-page app; neither is a secret. The tenant +// is served from a custom domain on the same site as letsbuilda.dev, so the +// SDK can restore a session from Auth0's cookie on each page load without +// keeping tokens in localStorage. +const AUTH0_DOMAIN = "id.letsbuilda.dev"; +const AUTH0_CLIENT_ID = "THoRnVJxMOfLxMAW0kaNW8nuP1q8q3qQ"; -if (!domain || !clientId) { - throw new Error( - "Missing Auth0 configuration. Set PUBLIC_AUTH0_DOMAIN and PUBLIC_AUTH0_CLIENT_ID.", - ); -} - -// State carried across the login redirect so we can return the user to the -// page they started from (Auth0 always redirects back to the origin). +// Carried across the login redirect so the visitor lands back on the page +// they started from (Auth0 always redirects to the site root). interface AppState { returnTo?: string; } let clientPromise: Promise | null = null; -// Finish the login redirect if this page received it, then forward the user to -// wherever they began. Safe to call on every page load. +// Finish a login redirect if this page load is one, then send the visitor on +// to where they started. A failed or cancelled login comes back as `?error=` +// and leaves them signed out on the home page. async function handleRedirect(client: Auth0Client): Promise { - const query = window.location.search; - if (!query.includes("code=") || !query.includes("state=")) { + const params = new URLSearchParams(window.location.search); + if (!params.has("state") || !(params.has("code") || params.has("error"))) { return; } - const { appState } = await client.handleRedirectCallback(); - window.history.replaceState({}, document.title, window.location.pathname); - - const returnTo = appState?.returnTo; - if (returnTo && returnTo !== window.location.pathname) { - window.location.assign(returnTo); + try { + const { appState } = await client.handleRedirectCallback(); + const returnTo = appState?.returnTo; + if (returnTo?.startsWith("/") && returnTo !== window.location.pathname) { + window.location.replace(returnTo); + return; + } + } catch (error) { + console.error("Login failed", error); } + window.history.replaceState({}, document.title, window.location.pathname); } -// Create the client once per page load and finish any login redirect before the -// promise resolves, so every caller observes the post-login state regardless of -// the order in which page scripts run. async function bootstrap(): Promise { + // Tokens stay in memory (the SDK default). Creating the client restores an + // existing session silently from Auth0's session cookie. const client = await createAuth0Client({ - domain, - clientId, - // Persist the session across this multi-page site's full page reloads and - // renew it with rotating refresh tokens instead of the hidden-iframe flow - // that modern browsers increasingly block. - useRefreshTokens: true, - cacheLocation: "localstorage", + domain: AUTH0_DOMAIN, + clientId: AUTH0_CLIENT_ID, authorizationParams: { redirect_uri: window.location.origin, - scope: "openid profile email", }, }); - await handleRedirect(client); return client; } +// One client per page load, created on first use. function getClient(): Promise { - if (!clientPromise) { - clientPromise = bootstrap(); - } + clientPromise ??= bootstrap(); return clientPromise; } -// Initialise auth for the current page (processing any login redirect) and -// report whether the visitor is logged in. Call once per page from the layout. -export async function initPage(): Promise { - const client = await getClient(); - return client.isAuthenticated(); -} - export async function isAuthenticated(): Promise { - const client = await getClient(); - return client.isAuthenticated(); + return (await getClient()).isAuthenticated(); } export async function getUser(): Promise { - const client = await getClient(); - return client.getUser(); + return (await getClient()).getUser(); } // Redirect to Auth0 to log in, returning to `returnTo` afterwards. @@ -95,7 +77,6 @@ export async function login( await client.loginWithRedirect({ appState: { returnTo } }); } -// Log out and return to the site root. export async function logout(): Promise { const client = await getClient(); await client.logout({ logoutParams: { returnTo: window.location.origin } }); diff --git a/src/pages/account.astro b/src/pages/account.astro index 2388fb1..0f01a5f 100644 --- a/src/pages/account.astro +++ b/src/pages/account.astro @@ -7,7 +7,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";