Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Deploy to GitHub Pages

# Publish the demonstrator's static surfaces (component gallery + wireframes,
# assembled by `npm run build:site`) to the gh-pages branch on every push to
# main. Per-PR previews live under pr-preview/ on the same branch and are
# preserved here via clean-exclude, so a main deploy never wipes open previews.

on:
push:
branches: [main]
workflow_dispatch:

# Serialise deploys to the same branch; do not cancel an in-flight publish.
concurrency:
group: pages-deploy
cancel-in-progress: false

permissions:
contents: write

jobs:
deploy:
name: Build and deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Assemble static site
run: npm run build:site
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SERVER_URL: ${{ github.server_url }}

- name: Deploy to gh-pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: site
# Preserve per-PR preview subtrees when refreshing the main site.
clean-exclude: pr-preview/
57 changes: 57 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: PR Preview

# For each pull request, publish a preview of the assembled static site to
# gh-pages under pr-preview/pr-<N>/ and link it from a PR comment. When the PR
# is merged or closed, the preview subtree is removed and the comment updated.
#
# Uses GITHUB_TOKEN, which has write access only for same-repository branches;
# previews are therefore not produced for pull requests from forks.

on:
pull_request:
types: [opened, reopened, synchronize, closed]

# One preview build per PR at a time; newer pushes supersede in-flight builds.
concurrency:
group: pr-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
preview:
name: Build and deploy preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# Skip the build on close: the deploy step only tears the preview down.
- name: Set up Node.js
if: github.event.action != 'closed'
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
if: github.event.action != 'closed'
run: npm ci

- name: Assemble static site
if: github.event.action != 'closed'
run: npm run build:site
env:
GITHUB_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SERVER_URL: ${{ github.server_url }}

- name: Deploy / tear down preview
uses: rossjrw/pr-preview-action@v1
with:
source-dir: site
umbrella-dir: pr-preview
# Deploy on open/reopen/synchronize; remove on close/merge.
action: auto
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,6 @@ vite.config.ts.timestamp-*

# Claude Code local settings (may contain machine-specific state)
.claude/settings.local.json

# Assembled static site for GitHub Pages (generated by npm run build:site)
site/
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test": "vitest run",
"typecheck": "tsc --noEmit",
"gallery": "tsx scripts/build-gallery.ts",
"build:site": "npm run gallery && tsx scripts/build-site.ts",
"bench": "tsx scripts/bench-canonical.ts"
},
"dependencies": {
Expand Down
89 changes: 89 additions & 0 deletions scripts/build-site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Assembles the publishable static site into `site/` for GitHub Pages.
*
* ASSAY has no compiled SPA — the demonstrator's shippable surfaces are static
* HTML: the fixture-backed component gallery (SPEC-14, `npm run gallery`) and
* the role-surface wireframes (a peer of the canonical set). This script
* gathers those into one directory with a small landing page, so the whole
* thing can be served from a single Pages root (or a per-PR preview subtree).
*
* Run via `npm run build:site` (which regenerates the gallery first). Output is
* generated and git-ignored; the CI workflows publish it, nothing is committed.
*
* Build metadata (commit, ref, PR number) is read from the environment when
* present so the landing page can label a preview honestly — no value is
* invented when a variable is absent.
*/
import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

const root = new URL('../', import.meta.url);
const site = new URL('site/', root);
const gallery = new URL('gallery/', site);

mkdirSync(fileURLToPath(gallery), { recursive: true });

// The demonstrator's two static surfaces, copied verbatim.
copyFileSync(
fileURLToPath(new URL('docs/assets/gallery/index.html', root)),
fileURLToPath(new URL('index.html', gallery)),
);
copyFileSync(
fileURLToPath(new URL('docs/assay-ui-wireframes.html', root)),
fileURLToPath(new URL('wireframes.html', site)),
);

const esc = (s: string): string =>
s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

// Honest build labelling: only render what the environment actually gives us.
const sha = process.env.GITHUB_SHA?.slice(0, 7);
const ref = process.env.GITHUB_REF_NAME;
const pr = process.env.PR_NUMBER;
const server = process.env.GITHUB_SERVER_URL ?? 'https://github.com';
const repo = process.env.GITHUB_REPOSITORY ?? 'DeepBlueCLtd/assay';

const badge = pr
? `preview of PR #${esc(pr)}`
: ref
? `${esc(ref)}`
: 'local build';
const commitLink = sha
? ` · <a style="color:inherit" href="${server}/${repo}/commit/${process.env.GITHUB_SHA}">${esc(sha)}</a>`
: '';

const card = (
href: string,
title: string,
body: string,
tag: string,
): string =>
`<a href="${href}" style="display:block;text-decoration:none;color:inherit;border:1px solid #D8DFE4;border-radius:8px;padding:18px 20px;margin-bottom:14px;background:#FCFDFD;transition:border-color .12s">
<div style="font-family:ui-monospace,monospace;font-size:10.5px;color:#5B6B77;text-transform:uppercase;letter-spacing:.06em">${tag}</div>
<div style="font-weight:600;font-size:16px;margin:4px 0 6px">${title} &rarr;</div>
<div style="font-size:13px;color:#5B6B77;line-height:1.55">${body}</div>
</a>`;

const html = `<!DOCTYPE html>
<!-- GENERATED by scripts/build-site.ts (npm run build:site) — do not edit by hand. -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ASSAY — demonstrator preview</title>
</head>
<body style="margin:0;background:#EDF0F2;color:#1B2732;font-family:system-ui,sans-serif;font-size:14px;line-height:1.5">
<div style="max-width:760px;margin:0 auto;padding:40px 24px 64px">
<div style="display:inline-block;font-family:ui-monospace,monospace;font-size:11px;color:#3E5D8A;background:#E6ECF6;border:1px solid #C8D5EA;border-radius:3px;padding:3px 9px;margin-bottom:16px">${badge}${commitLink}</div>
<h1 style="font-size:26px;border-bottom:3px solid #1B2732;padding-bottom:14px;margin-top:0">ASSAY</h1>
<p style="font-size:13.5px;color:#5B6B77">Assessment Semantics &amp; Scenario Analysis — a docs-first demonstrator. These are the demonstrator's <b>live static surfaces</b>, rendered over the Meridian Archipelago vignette (engineered fiction, ASSAY-DEC-8). No service, no plan, and no real operational picture sits behind them.</p>
${card('gallery/', 'Component gallery', "The demonstrator's actual components — band pills, provenance chips, the minimal S1 knowledge table and its four discipline moments — rendered over the real fixtures (SPEC-05/SPEC-14).", 'live components')}
${card('wireframes.html', 'UI wireframes', 'The four role surfaces on Meridian data, at the D+2 tableau moment (K9 supersedes K5; K12 contested). Frozen vignette identifiers only.', 'role surfaces')}
<p style="font-size:11.5px;color:#5B6B77;margin-top:28px">Banded honesty holds throughout: no scalar from an assessed source appears unbanded (constitution II). Identifiers are frozen per <code>assay-vignette.md</code> §8.</p>
</div>
</body>
</html>
`;

writeFileSync(fileURLToPath(new URL('index.html', site)), html);
console.log(`wrote site/ (${badge})`);
Loading