feat(adapter): plain-css second-adapter smoke — proves the IR seam (WHYNOT-WP-0002 T10)
A deliberately-unfinished second adapter that consumes the same ir/ and honours the same adapters/ADAPTER_CONTRACT.md (token full-gen + write-once stubs + drift roll-up shape + exit codes), with zero changes to ir/. Every IR component is 'new' for a fresh stack → 10 class stubs + tokens.css (80 props). Not a usable plain-CSS kit — the deliverable is confidence the boundary holds so the architecture can be lifted into a coulomb-level tool later. make adapt-plain-css. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
102
adapters/plain-css/adapt.mjs
Normal file
102
adapters/plain-css/adapt.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
// =============================================================
|
||||
// adapters/plain-css/adapt.mjs — second-adapter SMOKE (WHYNOT-WP-0002 · T10)
|
||||
//
|
||||
// NOT a finished stack. This exists only to prove the IR/adapter *boundary*:
|
||||
// a non-Lit adapter consuming the very same ir/ and honouring the same
|
||||
// adapters/ADAPTER_CONTRACT.md (token full-gen + stub/drift + exit codes).
|
||||
// The deliverable is confidence in the seam, not a usable plain-CSS kit.
|
||||
//
|
||||
// It does two contract things from ir/ and nothing Lit-specific:
|
||||
// • tokens → fully generated CSS custom properties (deterministic no-op re-run)
|
||||
// • components → write-once class stubs + a drift roll-up in the contract shape
|
||||
// (every component is "new" here — plain-CSS has no existing source to drift).
|
||||
//
|
||||
// Exit codes (shared contract): 0 ok · 2 usage · 3 drift/new · 5 internal.
|
||||
// Run: `make adapt-plain-css`.
|
||||
// =============================================================
|
||||
import { readFileSync, readdirSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const REPO = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const IR = join(REPO, "ir");
|
||||
const HERE = join(REPO, "adapters", "plain-css");
|
||||
const STUBS = join(HERE, "stubs");
|
||||
|
||||
const refToVar = (v) => {
|
||||
const m = /^\{[A-Za-z0-9]+\.([A-Za-z0-9-]+)\}$/.exec(String(v).trim());
|
||||
return m ? `var(--${m[1]})` : v;
|
||||
};
|
||||
|
||||
function generateTokens() {
|
||||
const tokens = JSON.parse(readFileSync(join(IR, "tokens.json"), "utf8"));
|
||||
const lines = ["/* @generated by adapters/plain-css (WHYNOT-WP-0002 T10) from ir/tokens.json — DO NOT EDIT. */", ":root {"];
|
||||
let n = 0;
|
||||
for (const [group, entries] of Object.entries(tokens)) {
|
||||
lines.push(` /* ${group} */`);
|
||||
for (const [key, tok] of Object.entries(entries)) {
|
||||
if (key === "$type") continue;
|
||||
lines.push(` --${key}: ${refToVar(tok.$value)};`); n++;
|
||||
}
|
||||
}
|
||||
lines.push("}", "");
|
||||
const out = join(HERE, "tokens.css");
|
||||
const body = lines.join("\n");
|
||||
const before = existsSync(out) ? readFileSync(out, "utf8") : null;
|
||||
if (before === body) { console.log(`tokens: up to date (${n} custom properties, no change).`); return; }
|
||||
writeFileSync(out, body);
|
||||
console.log(`tokens: regenerated ${n} custom properties → adapters/plain-css/tokens.css`);
|
||||
}
|
||||
|
||||
function renderClassStub(c) {
|
||||
const base = c.tag; // reuse the contract tag as the base class name
|
||||
const lines = [
|
||||
`/* @generated STUB by adapters/plain-css (T10) from ir/components/${c.name}.json — write-once. */`,
|
||||
`/* ${c.name}: ${c.description} */`,
|
||||
`.${base} { /* TODO: base appearance per ir/exemplars/${c.name}.html */ }`,
|
||||
];
|
||||
for (const v of c.variants || []) {
|
||||
for (const val of v.values) lines.push(`.${base}--${val} { /* ${v.axis}=${val} */ }`);
|
||||
}
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
|
||||
function scaffold() {
|
||||
const contracts = readdirSync(join(IR, "components")).filter((f) => f.endsWith(".json"))
|
||||
.map((f) => JSON.parse(readFileSync(join(IR, "components", f), "utf8")))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
// Plain-CSS has no hand-authored source, so every IR component is "new".
|
||||
const components = contracts.map((c) => ({ name: c.name, status: "new",
|
||||
issues: [{ kind: "no-counterpart", detail: `no .${c.tag} class — stub generated.` }] }));
|
||||
|
||||
mkdirSync(STUBS, { recursive: true });
|
||||
let stubbed = 0;
|
||||
for (const c of contracts) {
|
||||
const out = join(STUBS, `${c.tag}.css`);
|
||||
if (!existsSync(out)) { writeFileSync(out, renderClassStub(c)); stubbed++; }
|
||||
}
|
||||
// Drift roll-up — same shape as the Lit adapter's, proving the contract is portable.
|
||||
const reportPath = join(HERE, "_report.json");
|
||||
let generatedAt = new Date().toISOString();
|
||||
if (existsSync(reportPath)) {
|
||||
try { const prev = JSON.parse(readFileSync(reportPath, "utf8"));
|
||||
if (JSON.stringify(prev.components) === JSON.stringify(components) && prev.generatedAt) generatedAt = prev.generatedAt;
|
||||
} catch { /* fresh */ }
|
||||
}
|
||||
writeFileSync(reportPath, JSON.stringify({ stack: "plain-css", generatedAt, components }, null, 2) + "\n");
|
||||
console.log(`scaffold: ${components.length} components, all new (${stubbed} stub${stubbed === 1 ? "" : "s"} written) → adapters/plain-css/stubs/`);
|
||||
return components.length > 0;
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!existsSync(join(IR, "tokens.json"))) { console.error("No ir/ — run `make ir` first."); process.exit(2); }
|
||||
generateTokens();
|
||||
const isNew = scaffold();
|
||||
console.log("\nadapt-plain-css: SMOKE only — proves a non-Lit adapter consumes the same ir/ and");
|
||||
console.log("emits the same contract shapes. Not a finished stack (see adapters/plain-css/README.md).");
|
||||
if (isNew) process.exit(3); // new components present (contract: 3) — expected for a fresh stack
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user