Skip to content
Open
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
57 changes: 0 additions & 57 deletions .github/workflows/aggregate-capabilities.yml

This file was deleted.

6 changes: 5 additions & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -54,6 +56,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:
Expand Down
8 changes: 6 additions & 2 deletions scripts/capability-matrix/src/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -94,10 +95,13 @@ async function main(): Promise<void> {
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`);
}

Expand Down
54 changes: 30 additions & 24 deletions scripts/capability-matrix/src/generate-site.ts
Original file line number Diff line number Diff line change
@@ -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 ────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -44,18 +44,6 @@ function clamp01(n: number) {
return Math.max(0, Math.min(1, n));
}

function featureParity(feature: Feature, compliance: Partial<Record<Language, ComplianceMap>>): 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";
Expand Down Expand Up @@ -89,7 +77,7 @@ function statusCell(feature: Feature, lang: Language, compliance: Partial<Record

// ── Area table ───────────────────────────────────────────────────────────────

function renderArea(loaded: LoadedArea, compliance: Partial<Record<Language, ComplianceMap>>, specs: Set<string>): string {
function renderArea(loaded: LoadedArea, compliance: Partial<Record<Language, ComplianceMap>>, parity: ParityReport, specs: Set<string>): string {
const { area } = loaded;

// Group features preserving insertion order
Expand All @@ -104,7 +92,7 @@ function renderArea(loaded: LoadedArea, compliance: Partial<Record<Language, Com
for (const [group, features] of groups) {
rows += `<tr class="group-row"><td colspan="${LANGUAGES.length + 2}">${esc(group)}</td></tr>\n`;
for (const f of features) {
const fp = featureParity(f, compliance);
const fp = parity.perFeature[f.id] ?? 0;
const nameHtml = specs.has(f.id)
? `<a class="feature-spec-link" href="${esc(`${SPEC_GITHUB_BASE}/${f.id.replaceAll(".", "/")}.md`)}" target="_blank" rel="noopener noreferrer">${esc(f.name)}</a>`
: esc(f.name);
Expand Down Expand Up @@ -146,10 +134,10 @@ ${rows} </tbody>
export function renderHtml(
areas: LoadedArea[],
compliance: Partial<Record<Language, ComplianceMap>>,
parity: ParityReport,
specs: Set<string>,
buildDate: string
): string {
const parity = computeParity(areas, compliance);

const navLinks = areas
.map(
Expand Down Expand Up @@ -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 `<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -465,7 +455,7 @@ export function renderHtml(
<div class="header-top">
<div>
<h1 class="site-title"><span>Supabase</span> SDK Capability Matrix</h1>
<p class="build-info">Updated ${esc(buildDate)}</p>
<p class="build-info">Updated ${esc(buildDate)} · <a href="compliance.json">compliance.json</a></p>
</div>
<div class="overall-badge">
<span class="label">Overall parity</span>
Expand Down Expand Up @@ -535,23 +525,39 @@ function main() {
const outDir = join(root, "site");
const compliancePath = process.argv[2];

const compliance: Partial<Record<Language, ComplianceMap>> = 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<Record<Language, ComplianceMap>> = {};
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();
12 changes: 5 additions & 7 deletions scripts/capability-matrix/src/report.ts
Original file line number Diff line number Diff line change
@@ -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<string, number>;
perLanguage: Record<Language, number>;
}
export type { ParityReport };

const mean = (xs: number[]): number =>
xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
Expand All @@ -16,6 +12,7 @@ export function computeParity(
): ParityReport {
const featureParities: number[] = [];
const perAreaParities: Record<string, number[]> = {};
const perFeature: Record<string, number> = {};
const langImplemented = Object.fromEntries(LANGUAGES.map((l) => [l, 0])) as Record<Language, number>;
const langApplicable = Object.fromEntries(LANGUAGES.map((l) => [l, 0])) as Record<Language, number>;

Expand All @@ -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;
}
}

Expand All @@ -47,5 +45,5 @@ export function computeParity(
LANGUAGES.map((l) => [l, langApplicable[l] === 0 ? 0 : langImplemented[l] / langApplicable[l]])
) as Record<Language, number>;

return { overall: mean(featureParities), perArea, perLanguage };
return { overall: mean(featureParities), perArea, perLanguage, perFeature };
}
14 changes: 14 additions & 0 deletions scripts/capability-matrix/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,17 @@ export interface ComplianceEntry {

// Feature ID → ComplianceEntry (sparse; unlisted features default to not_implemented)
export type ComplianceMap = Record<string, ComplianceEntry>;

export interface ParityReport {
overall: number;
perArea: Record<string, number>;
perLanguage: Record<Language, number>;
/** Cross-language parity score per feature ID (0–1). */
perFeature: Record<string, number>;
}

/** Shape of site/compliance.json — raw compliance data plus precomputed parity. */
export interface ComplianceFile {
compliance: Partial<Record<Language, ComplianceMap>>;
parity: ParityReport;
}