From 95d378e00c0bd8285b49cbc3f558e7622692b3fb Mon Sep 17 00:00:00 2001 From: trivedi-vatsal Date: Sat, 29 Aug 2026 07:46:31 +0530 Subject: [PATCH 1/2] feat(site): restore the live demo Check Run section PR links for openpreflight/demo are filled; shareable run URLs stay empty until the demo instance produces Check Runs. Co-authored-by: Cursor --- package.json | 1 + public/index.md | 8 ++ scripts/check-links.mjs | 21 +++ scripts/refresh-demo-runs.mjs | 144 +++++++++++++++++++ src/components/blocks/site-header-01.tsx | 1 + src/components/templates/saas-landing-01.tsx | 97 +++++++++++++ src/data/demo-runs.json | 60 ++++++++ tsconfig.json | 1 + 8 files changed, 333 insertions(+) create mode 100644 scripts/refresh-demo-runs.mjs create mode 100644 src/data/demo-runs.json diff --git a/package.json b/package.json index e7ccb14..d41ee13 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build": "astro build", "preview": "astro preview", "check-links": "node scripts/check-links.mjs", + "refresh-demo-runs": "node scripts/refresh-demo-runs.mjs", "astro": "astro" }, "dependencies": { diff --git a/public/index.md b/public/index.md index 3c39f88..f13834c 100644 --- a/public/index.md +++ b/public/index.md @@ -21,6 +21,14 @@ Configuration happens in the web UI: register GitHub Apps, bind repos, and mint 2. The worker mints an installation token, opens a Check Run, fetches the exact commit, detaches the checkout, and strips the remote before any step runs. 3. Pipeline steps run in-process, or via `docker run` when `runtime:` is set. The Check Run carries a truncated log tail, and the full log stays on the details page. +## Live demo + +Six pull requests on [openpreflight/demo](https://github.com/openpreflight/demo) produce real Check Runs on a self-hosted instance (not GitHub Actions). The log pages are the same `/runs/{id}` pages you get behind auth; shareable logs are on for that binding only. + +Outcomes: passing, failing test, failing build, timeout, skipped, container runtime. Links to individual run pages can 404 after log retention prunes a job — the site falls back to the pull request. + +See https://openpreflight.xyz/#demo + ## Not in v1 - GitHub Actions YAML diff --git a/scripts/check-links.mjs b/scripts/check-links.mjs index 24896e4..4783705 100644 --- a/scripts/check-links.mjs +++ b/scripts/check-links.mjs @@ -35,6 +35,27 @@ const allowedExternalOrigins = [ 'https://www.apache.org/licenses/LICENSE-2.0', ]; +// Shareable demo log pages live on whatever host public_base_url is. Allow +// those origins from demo-runs.json so a refresh cannot break link CI. +try { + const demoRuns = JSON.parse( + readFileSync(join(root, 'src', 'data', 'demo-runs.json'), 'utf8'), + ); + for (const run of demoRuns.runs ?? []) { + if (typeof run.runUrl !== 'string' || !run.runUrl) continue; + try { + const origin = new URL(run.runUrl).origin; + if (origin.startsWith('https://') && !allowedExternalOrigins.includes(origin)) { + allowedExternalOrigins.push(origin); + } + } catch { + /* ignore unparseable runUrl; the HTML check will still flag it */ + } + } +} catch { + /* file missing until the demo section lands */ +} + const missing = required.filter((p) => !existsSync(join(dist, p))); if (missing.length) { console.error('Missing required dist paths:'); diff --git a/scripts/refresh-demo-runs.mjs b/scripts/refresh-demo-runs.mjs new file mode 100644 index 0000000..1872741 --- /dev/null +++ b/scripts/refresh-demo-runs.mjs @@ -0,0 +1,144 @@ +#!/usr/bin/env node +/** + * Rewrite src/data/demo-runs.json from public GitHub Checks on openpreflight/demo. + * + * Never calls the demo instance. UI uses prUrl always; runUrl is optional. + * If a Check Run cannot be resolved, prUrl stays and runUrl is cleared — never + * invent a /runs/ URL that would 404. + * + * Usage (from website/): + * npm run refresh-demo-runs + * + * Optional: GITHUB_TOKEN for a higher API rate limit. Unauthenticated works + * on a public repo. + */ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const dataPath = join(root, "src", "data", "demo-runs.json"); +const OWNER = "openpreflight"; +const REPO = "demo"; +const CHECK_NAME = /openpreflight/i; +const RUN_PATH = /\/runs\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || ""; + +/** @param {string} path */ +async function gh(path) { + const headers = { + Accept: "application/vnd.github+json", + "User-Agent": "openpreflight-refresh-demo-runs", + "X-GitHub-Api-Version": "2022-11-28", + }; + if (token) headers.Authorization = `Bearer ${token}`; + const res = await fetch(`https://api.github.com${path}`, { headers }); + if (!res.ok) { + const body = await res.text(); + throw new Error(`GitHub ${res.status} ${path}: ${body.slice(0, 400)}`); + } + return res.json(); +} + +/** @param {unknown} url */ +function shareableRunUrl(url) { + if (typeof url !== "string" || !url) return null; + try { + const parsed = new URL(url); + if (parsed.protocol !== "https:") return null; + if (!RUN_PATH.test(parsed.pathname)) return null; + return parsed.href; + } catch { + return null; + } +} + +/** + * @param {Array<{ name?: string, details_url?: string, completed_at?: string | null, started_at?: string | null }>} checks + */ +function pickCheck(checks) { + const named = checks.filter((c) => CHECK_NAME.test(c.name ?? "")); + const pool = named.length ? named : checks; + return [...pool].sort((a, b) => { + const ta = Date.parse(a.completed_at || a.started_at || 0); + const tb = Date.parse(b.completed_at || b.started_at || 0); + return tb - ta; + })[0]; +} + +const data = JSON.parse(readFileSync(dataPath, "utf8")); +if (!data?.runs?.length) { + console.error("demo-runs.json has no runs[]"); + process.exit(1); +} + +let pulls; +try { + pulls = await gh(`/repos/${OWNER}/${REPO}/pulls?state=open&per_page=100`); +} catch (err) { + console.error( + "Could not list PRs; leaving demo-runs.json unchanged.\n", + err instanceof Error ? err.message : err, + ); + process.exit(1); +} + +if (!Array.isArray(pulls)) { + console.error("Unexpected pulls payload; leaving demo-runs.json unchanged."); + process.exit(1); +} + +/** @type {Map} */ +const byBranch = new Map(); +for (const pr of pulls) { + const ref = pr.head?.ref; + const sha = pr.head?.sha; + if (typeof ref === "string" && typeof sha === "string" && pr.html_url) { + byBranch.set(ref, { html_url: pr.html_url, sha }); + } +} + +let resolved = 0; +let missingRun = 0; + +for (const run of data.runs) { + const pr = byBranch.get(run.branch); + if (pr) { + run.prUrl = pr.html_url; + } + // Always keep a GitHub link even if the PR is gone. + if (!run.prUrl) { + run.prUrl = `https://github.com/${OWNER}/${REPO}`; + } + + if (!pr) { + run.runUrl = null; + missingRun += 1; + continue; + } + + try { + const payload = await gh( + `/repos/${OWNER}/${REPO}/commits/${pr.sha}/check-runs`, + ); + const check = pickCheck(payload.check_runs ?? []); + const url = shareableRunUrl(check?.details_url); + run.runUrl = url; + if (url) resolved += 1; + else missingRun += 1; + } catch (err) { + console.error( + `check-runs failed for ${run.branch}; clearing runUrl.\n`, + err instanceof Error ? err.message : err, + ); + run.runUrl = null; + missingRun += 1; + } +} + +data.updatedAt = new Date().toISOString(); +writeFileSync(dataPath, `${JSON.stringify(data, null, 2)}\n`); +console.log( + `wrote ${dataPath}: ${resolved} runUrl(s), ${missingRun} without a shareable log (prUrl kept).`, +); diff --git a/src/components/blocks/site-header-01.tsx b/src/components/blocks/site-header-01.tsx index 3b5b40c..a2b23e3 100644 --- a/src/components/blocks/site-header-01.tsx +++ b/src/components/blocks/site-header-01.tsx @@ -47,6 +47,7 @@ const defaultLinks: SiteHeaderLink[] = [ { label: "Product", href: "#product" }, { label: "How it runs", href: "#how" }, { label: "Pipeline", href: "#pipeline" }, + { label: "Demo", href: "#demo" }, { label: "Docs", href: "https://docs.openpreflight.xyz" }, ]; diff --git a/src/components/templates/saas-landing-01.tsx b/src/components/templates/saas-landing-01.tsx index 9f70000..3ecb8f6 100644 --- a/src/components/templates/saas-landing-01.tsx +++ b/src/components/templates/saas-landing-01.tsx @@ -17,9 +17,40 @@ import { SiteHeader01 } from "@/components/blocks/site-header-01"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +import demoRuns from "@/data/demo-runs.json"; const DOCS = "https://docs.openpreflight.xyz"; const REPO = "https://github.com/openpreflight/openpreflight"; +const DEMO_REPO = "https://github.com/openpreflight/demo"; + +function outcomeLabel(conclusion: string) { + switch (conclusion) { + case "success": + return "passed"; + case "failure": + return "failed"; + case "timed_out": + return "timed out"; + case "skipped": + return "skipped"; + default: + return conclusion; + } +} + +function outcomeClass(conclusion: string) { + switch (conclusion) { + case "success": + return "text-[var(--pass,#2f6f4f)]"; + case "failure": + case "timed_out": + return "text-red-700 dark:text-red-400"; + case "skipped": + return "text-muted-foreground"; + default: + return "text-muted-foreground"; + } +} const checkSteps = [ { name: "install", command: "npm ci", duration: "8s", width: "19%" }, { name: "test", command: "go test ./...", duration: "21s", width: "50%" }, @@ -90,6 +121,7 @@ function SaasLanding01({ className, ...props }: React.ComponentProps<"div">) { { label: "Product", href: "#product" }, { label: "How it runs", href: "#how" }, { label: "Pipeline", href: "#pipeline" }, + { label: "Demo", href: "#demo" }, { label: "Docs", href: DOCS }, ]} ctaLabel="Quickstart" @@ -364,6 +396,71 @@ timeout: 15m`} +
+
+ + Live demo + +

+ Real Check Runs, public log pages +

+

+ These are real Check Runs on{" "} + + openpreflight/demo + + , produced by a self-hosted instance — not GitHub Actions. The + log pages are the same pages you get behind auth; shareable logs + are on for that one binding. A run URL can 404 after retention + prunes the job — the pull request stays. +

+
    + {demoRuns.runs.map((entry) => ( +
  • +

    + {outcomeLabel(entry.conclusion)} +

    +

    + {entry.title} +

    +

    + {entry.outcome} +

    +
    + {entry.runUrl ? ( + + run log + + ) : null} + + pull request + +
    +
  • + ))} +
+

+ The demo instance is a live box and may occasionally be down. If a + run link is missing, use the pull request — the Check Run on + GitHub still points at the details URL when the instance is up. +

+
+
+
diff --git a/src/data/demo-runs.json b/src/data/demo-runs.json new file mode 100644 index 0000000..0d9e8c4 --- /dev/null +++ b/src/data/demo-runs.json @@ -0,0 +1,60 @@ +{ + "repo": "openpreflight/demo", + "updatedAt": "2026-08-29T02:14:18.799Z", + "runs": [ + { + "id": "passing", + "title": "Passing", + "outcome": "three steps, all green", + "branch": "demo/passing", + "conclusion": "success", + "prUrl": "https://github.com/openpreflight/demo/pull/1", + "runUrl": null + }, + { + "id": "failing-test", + "title": "Failing test", + "outcome": "real node:test assertion failure", + "branch": "demo/failing-test", + "conclusion": "failure", + "prUrl": "https://github.com/openpreflight/demo/pull/2", + "runUrl": null + }, + { + "id": "failing-build", + "title": "Failing build", + "outcome": "real build / syntax error", + "branch": "demo/failing-build", + "conclusion": "failure", + "prUrl": "https://github.com/openpreflight/demo/pull/3", + "runUrl": null + }, + { + "id": "timeout", + "title": "Timeout", + "outcome": "pipeline deadline (timeout: 5s, sleep 60)", + "branch": "demo/timeout", + "conclusion": "timed_out", + "prUrl": "https://github.com/openpreflight/demo/pull/4", + "runUrl": null + }, + { + "id": "skipped", + "title": "Skipped", + "outcome": "nothing to run → Check Run skipped", + "branch": "demo/skipped", + "conclusion": "skipped", + "prUrl": "https://github.com/openpreflight/demo/pull/5", + "runUrl": null + }, + { + "id": "container", + "title": "Container", + "outcome": "runtime: node:24 via Docker", + "branch": "demo/container", + "conclusion": "success", + "prUrl": "https://github.com/openpreflight/demo/pull/6", + "runUrl": null + } + ] +} diff --git a/tsconfig.json b/tsconfig.json index 25c7bdb..05a3bfb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ ], "compilerOptions": { "baseUrl": ".", + "resolveJsonModule": true, "paths": { "@/*": [ "./src/*" From a9e7d7e530a8f147d5a753cd9f2b9ff494643b26 Mon Sep 17 00:00:00 2001 From: trivedi-vatsal Date: Sat, 29 Aug 2026 07:55:16 +0530 Subject: [PATCH 2/2] chore: point demo cards at the reopened pull requests Co-authored-by: Cursor --- src/data/demo-runs.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/data/demo-runs.json b/src/data/demo-runs.json index 0d9e8c4..dc7db07 100644 --- a/src/data/demo-runs.json +++ b/src/data/demo-runs.json @@ -1,6 +1,6 @@ { "repo": "openpreflight/demo", - "updatedAt": "2026-08-29T02:14:18.799Z", + "updatedAt": "2026-08-29T02:25:16.474Z", "runs": [ { "id": "passing", @@ -8,7 +8,7 @@ "outcome": "three steps, all green", "branch": "demo/passing", "conclusion": "success", - "prUrl": "https://github.com/openpreflight/demo/pull/1", + "prUrl": "https://github.com/openpreflight/demo/pull/7", "runUrl": null }, { @@ -17,7 +17,7 @@ "outcome": "real node:test assertion failure", "branch": "demo/failing-test", "conclusion": "failure", - "prUrl": "https://github.com/openpreflight/demo/pull/2", + "prUrl": "https://github.com/openpreflight/demo/pull/8", "runUrl": null }, { @@ -26,7 +26,7 @@ "outcome": "real build / syntax error", "branch": "demo/failing-build", "conclusion": "failure", - "prUrl": "https://github.com/openpreflight/demo/pull/3", + "prUrl": "https://github.com/openpreflight/demo/pull/9", "runUrl": null }, { @@ -35,7 +35,7 @@ "outcome": "pipeline deadline (timeout: 5s, sleep 60)", "branch": "demo/timeout", "conclusion": "timed_out", - "prUrl": "https://github.com/openpreflight/demo/pull/4", + "prUrl": "https://github.com/openpreflight/demo/pull/10", "runUrl": null }, { @@ -44,7 +44,7 @@ "outcome": "nothing to run → Check Run skipped", "branch": "demo/skipped", "conclusion": "skipped", - "prUrl": "https://github.com/openpreflight/demo/pull/5", + "prUrl": "https://github.com/openpreflight/demo/pull/11", "runUrl": null }, { @@ -53,7 +53,7 @@ "outcome": "runtime: node:24 via Docker", "branch": "demo/container", "conclusion": "success", - "prUrl": "https://github.com/openpreflight/demo/pull/6", + "prUrl": "https://github.com/openpreflight/demo/pull/12", "runUrl": null } ]