From a10f21599d15754bb1dbaba6bd75b89bb83c3080 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 25 Jun 2026 15:01:10 -0300 Subject: [PATCH 1/2] feat(site): serve compliance.json with precomputed parity from GitHub Pages Enrich site/compliance.json to include parity scores so external sources can query them without recomputing, and update the site to render from those precomputed values rather than computing them at build time. - Add `ParityReport` (with new `perFeature` field) and `ComplianceFile` wrapper type to types.ts as canonical exported types - report.ts: compute and return `perFeature` (cross-language parity per feature ID) alongside the existing overall/perArea/perLanguage values; re-export ParityReport from types for backward compat - aggregate.ts: call computeParity after fetching all compliance data and write a ComplianceFile envelope (compliance + parity) to compliance.json - generate-site.ts: read precomputed parity from the ComplianceFile envelope; fall back to computeParity when no file is provided (local dev); remove featureParity() helper; add link to compliance.json in the site header; explicitly copy compliance.json to the output dir so it is always present regardless of the input path - Both deploy workflows: document that the Pages artifact intentionally contains both index.html and compliance.json --- .github/workflows/aggregate-capabilities.yml | 2 + .github/workflows/deploy-pages.yml | 2 + scripts/capability-matrix/src/aggregate.ts | 8 ++- .../capability-matrix/src/generate-site.ts | 54 ++++++++++--------- scripts/capability-matrix/src/report.ts | 12 ++--- scripts/capability-matrix/src/types.ts | 14 +++++ 6 files changed, 59 insertions(+), 33 deletions(-) diff --git a/.github/workflows/aggregate-capabilities.yml b/.github/workflows/aggregate-capabilities.yml index 8504e56..47aa984 100644 --- a/.github/workflows/aggregate-capabilities.yml +++ b/.github/workflows/aggregate-capabilities.yml @@ -50,6 +50,8 @@ jobs: - name: Upload Pages artifact uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: + # Uploads site/index.html (the matrix UI) and site/compliance.json + # (machine-readable compliance data, publicly queryable by external tools). path: site/ - name: Deploy to GitHub Pages diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 49121c3..eb3e7a6 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -54,6 +54,8 @@ jobs: - name: Upload Pages artifact uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: + # Uploads site/index.html (the matrix UI) and site/compliance.json + # (machine-readable compliance data, publicly queryable by external tools). path: site/ deploy: diff --git a/scripts/capability-matrix/src/aggregate.ts b/scripts/capability-matrix/src/aggregate.ts index a092585..c9f1775 100644 --- a/scripts/capability-matrix/src/aggregate.ts +++ b/scripts/capability-matrix/src/aggregate.ts @@ -5,7 +5,8 @@ import { parse } from "yaml"; import { loadAreas } from "./load.js"; import { validateCompliance, normalizeCompliance, collectFeatureIds } from "./compliance.js"; import type { RawCompliance } from "./compliance.js"; -import type { ComplianceMap, Language } from "./types.js"; +import { computeParity } from "./report.js"; +import type { ComplianceFile, ComplianceMap, Language } from "./types.js"; interface Repo { slug: string; @@ -94,10 +95,13 @@ async function main(): Promise { process.exit(1); } + const parity = computeParity(areas, result); + const outDir = join(root, "site"); mkdirSync(outDir, { recursive: true }); const outPath = join(outDir, "compliance.json"); - writeFileSync(outPath, JSON.stringify(result, null, 2), "utf8"); + const file: ComplianceFile = { compliance: result, parity }; + writeFileSync(outPath, JSON.stringify(file, null, 2), "utf8"); console.log(`Compliance data written to site/compliance.json`); } diff --git a/scripts/capability-matrix/src/generate-site.ts b/scripts/capability-matrix/src/generate-site.ts index 9aadc07..d08f687 100644 --- a/scripts/capability-matrix/src/generate-site.ts +++ b/scripts/capability-matrix/src/generate-site.ts @@ -1,10 +1,10 @@ -import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { loadAreas } from "./load.js"; import { computeParity } from "./report.js"; import { LANGUAGES } from "./types.js"; -import type { ComplianceMap, Feature, Language, LoadedArea } from "./types.js"; +import type { ComplianceFile, ComplianceMap, Feature, Language, LoadedArea, ParityReport } from "./types.js"; // ── Constants ──────────────────────────────────────────────────────────────── @@ -44,18 +44,6 @@ function clamp01(n: number) { return Math.max(0, Math.min(1, n)); } -function featureParity(feature: Feature, compliance: Partial>): number { - let impl = 0; - let applicable = 0; - for (const lang of LANGUAGES) { - const status = compliance[lang]?.[feature.id]?.status ?? "not_implemented"; - if (status === "not_applicable") continue; - applicable++; - if (status === "implemented" || status === "partially_implemented") impl++; - } - return applicable === 0 ? 1 : impl / applicable; -} - function parityClass(n: number): string { if (n >= 0.8) return "parity-high"; if (n >= 0.4) return "parity-mid"; @@ -89,7 +77,7 @@ function statusCell(feature: Feature, lang: Language, compliance: Partial>, specs: Set): string { +function renderArea(loaded: LoadedArea, compliance: Partial>, parity: ParityReport, specs: Set): string { const { area } = loaded; // Group features preserving insertion order @@ -104,7 +92,7 @@ function renderArea(loaded: LoadedArea, compliance: Partial${esc(group)}\n`; for (const f of features) { - const fp = featureParity(f, compliance); + const fp = parity.perFeature[f.id] ?? 0; const nameHtml = specs.has(f.id) ? `${esc(f.name)}` : esc(f.name); @@ -146,10 +134,10 @@ ${rows} export function renderHtml( areas: LoadedArea[], compliance: Partial>, + parity: ParityReport, specs: Set, buildDate: string ): string { - const parity = computeParity(areas, compliance); const navLinks = areas .map( @@ -182,7 +170,7 @@ export function renderHtml( }) .join(""); - const areaSections = areas.map((a) => renderArea(a, compliance, specs)).join("\n"); + const areaSections = areas.map((a) => renderArea(a, compliance, parity, specs)).join("\n"); return ` @@ -230,6 +218,8 @@ export function renderHtml( color: #888; margin-top: 0.25rem; } + .build-info a { color: #3ECF8E; text-decoration: none; } + .build-info a:hover { text-decoration: underline; } .overall-badge { display: flex; align-items: center; @@ -465,7 +455,7 @@ export function renderHtml(

Supabase SDK Capability Matrix

-

Updated ${esc(buildDate)}

+

Updated ${esc(buildDate)} · compliance.json

Overall parity @@ -535,23 +525,39 @@ function main() { const outDir = join(root, "site"); const compliancePath = process.argv[2]; - const compliance: Partial> = compliancePath - ? JSON.parse(readFileSync(compliancePath, "utf8")) - : {}; - const { areas, findings } = loadAreas(capDir); if (findings.length > 0) { for (const f of findings) console.error(`${f.level.toUpperCase()} ${f.file}: ${f.message}`); if (findings.some((f) => f.level === "error")) process.exit(1); } + let compliance: Partial> = {}; + let parity = computeParity(areas, compliance); + + if (compliancePath) { + const fileData = JSON.parse(readFileSync(compliancePath, "utf8")) as ComplianceFile; + compliance = fileData.compliance; + parity = fileData.parity; + } + const specs = buildSpecSet(root); const buildDate = new Date().toISOString().slice(0, 10); - const html = renderHtml(areas, compliance, specs, buildDate); + const html = renderHtml(areas, compliance, parity, specs, buildDate); mkdirSync(outDir, { recursive: true }); writeFileSync(join(outDir, "index.html"), html, "utf8"); console.log(`Site built → site/index.html (${(html.length / 1024).toFixed(1)} KB)`); + + // Ensure compliance.json is in the output directory so it is served by + // GitHub Pages alongside index.html and is queryable by external sources. + if (compliancePath) { + const dataOut = join(outDir, "compliance.json"); + const resolvedSrc = resolve(compliancePath); + if (resolvedSrc !== dataOut) { + copyFileSync(resolvedSrc, dataOut); + } + console.log(`Compliance data published → site/compliance.json`); + } } main(); diff --git a/scripts/capability-matrix/src/report.ts b/scripts/capability-matrix/src/report.ts index df49d7a..93d9757 100644 --- a/scripts/capability-matrix/src/report.ts +++ b/scripts/capability-matrix/src/report.ts @@ -1,11 +1,7 @@ import { LANGUAGES } from "./types.js"; -import type { ComplianceMap, Language, LoadedArea } from "./types.js"; +import type { ComplianceMap, Language, LoadedArea, ParityReport } from "./types.js"; -export interface ParityReport { - overall: number; - perArea: Record; - perLanguage: Record; -} +export type { ParityReport }; const mean = (xs: number[]): number => xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length; @@ -16,6 +12,7 @@ export function computeParity( ): ParityReport { const featureParities: number[] = []; const perAreaParities: Record = {}; + const perFeature: Record = {}; const langImplemented = Object.fromEntries(LANGUAGES.map((l) => [l, 0])) as Record; const langApplicable = Object.fromEntries(LANGUAGES.map((l) => [l, 0])) as Record; @@ -37,6 +34,7 @@ export function computeParity( const parity = applicable === 0 ? 1 : implemented / applicable; featureParities.push(parity); perAreaParities[area.area].push(parity); + perFeature[feature.id] = parity; } } @@ -47,5 +45,5 @@ export function computeParity( LANGUAGES.map((l) => [l, langApplicable[l] === 0 ? 0 : langImplemented[l] / langApplicable[l]]) ) as Record; - return { overall: mean(featureParities), perArea, perLanguage }; + return { overall: mean(featureParities), perArea, perLanguage, perFeature }; } diff --git a/scripts/capability-matrix/src/types.ts b/scripts/capability-matrix/src/types.ts index e701753..2dbfa6b 100644 --- a/scripts/capability-matrix/src/types.ts +++ b/scripts/capability-matrix/src/types.ts @@ -56,3 +56,17 @@ export interface ComplianceEntry { // Feature ID → ComplianceEntry (sparse; unlisted features default to not_implemented) export type ComplianceMap = Record; + +export interface ParityReport { + overall: number; + perArea: Record; + perLanguage: Record; + /** Cross-language parity score per feature ID (0–1). */ + perFeature: Record; +} + +/** Shape of site/compliance.json — raw compliance data plus precomputed parity. */ +export interface ComplianceFile { + compliance: Partial>; + parity: ParityReport; +} From 11e3fd84b6b706c4b627f1881566c61bbbbf1144 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 25 Jun 2026 15:04:25 -0300 Subject: [PATCH 2/2] ci: merge aggregate and deploy workflows into one --- .github/workflows/aggregate-capabilities.yml | 59 -------------------- .github/workflows/deploy-pages.yml | 4 +- 2 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/aggregate-capabilities.yml diff --git a/.github/workflows/aggregate-capabilities.yml b/.github/workflows/aggregate-capabilities.yml deleted file mode 100644 index 47aa984..0000000 --- a/.github/workflows/aggregate-capabilities.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Aggregate SDK Compliance - -on: - schedule: - - cron: "0 2 * * *" - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -defaults: - run: - working-directory: scripts/capability-matrix - -jobs: - aggregate-and-deploy: - name: Aggregate compliance and deploy site - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "22" - - - name: Install dependencies - run: npm ci - - - name: Aggregate compliance data from SDK repos - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run aggregate - - - name: Build site with compliance data - run: npm run build-site -- ../../site/compliance.json - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - with: - # Uploads site/index.html (the matrix UI) and site/compliance.json - # (machine-readable compliance data, publicly queryable by external tools). - path: site/ - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index eb3e7a6..7c131cb 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -8,7 +8,9 @@ on: - "schema/**" - "scripts/capability-matrix/**" - ".github/workflows/deploy-pages.yml" - workflow_dispatch: # allow manual trigger + schedule: + - cron: "0 2 * * *" # daily refresh to pick up SDK compliance updates + workflow_dispatch: # Required for GitHub Pages OIDC deployment permissions: