Ctrl K
ComunidadGuías…

scripts/build_extension_frontends.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Baut ALLE Extension-Frontend-Bundles auf einmal -- ersetzt den bisher manuellen
 * `node build.mjs`-Lauf pro Extension.
 *
 * Zwei Modi:
 *   node scripts/build_extension_frontends.mjs            baut alle Bundles neu
 *   node scripts/build_extension_frontends.mjs --check     prueft nur, ob das
 *     committete `dist/index.js` einem frischen Build entspricht, OHNE das
 *     Arbeitsverzeichnis zu veraendern (Exit-Code != 0 + Liste der Abweichungen,
 *     wenn nicht). Das deckt den eigentlichen Fehlerfall ab: eine Aenderung
 *     erfordert einen manuellen Rebuild, sonst dient der Live-Demo ein veraltetes
 *     Bundle. `--check` ist die CI-/Pre-Deploy-Gegenprobe dazu, analog
 *     `scripts/check_core_purity.py`.
 */
import { execFileSync } from "node:child_process";
import { copyFileSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const extensionsDir = path.join(repoRoot, "extensions");
const checkOnly = process.argv.includes("--check");

function findBuildScripts() {
  if (!existsSync(extensionsDir)) return [];
  return readdirSync(extensionsDir, { withFileTypes: true })
    .filter((e) => e.isDirectory())
    .map((e) => ({
      extName: e.name,
      buildScript: path.join(extensionsDir, e.name, "frontend", "build.mjs"),
      distFile: path.join(extensionsDir, e.name, "frontend", "dist", "index.js"),
    }))
    .filter((e) => existsSync(e.buildScript));
}

/**
 * cwd MUSS das jeweilige `frontend/`-Verzeichnis sein, nicht das Repo-Root --
 * esbuild betten den Eintragspfad in einen Quellkommentar relativ zum
 * aufrufenden Arbeitsverzeichnis ein (`// src/Foo.tsx` vs. `// extensions/.../
 * src/Foo.tsx`), sonst weicht jeder frische Build spurios vom manuell mit
 * `cd frontend && node build.mjs` erzeugten Bundle ab.
 */
function runBuild(buildScript) {
  execFileSync(process.execPath, [buildScript], { cwd: path.dirname(buildScript), stdio: "pipe" });
}

function buildAll(targets) {
  const failed = [];
  for (const { extName, buildScript } of targets) {
    process.stdout.write(`-- ${extName} --\n`);
    try {
      runBuild(buildScript);
    } catch (err) {
      process.stderr.write(`FEHLER beim Bauen von "${extName}":\n${err.stderr?.toString() ?? err.message}\n`);
      failed.push(extName);
    }
  }
  return failed;
}

/** Baut jede Extension in eine WEGWERF-Kopie ihres dist/-Ordners (nie das
 * committete Bundle ueberschreiben) und vergleicht den Inhalt. */
function checkAll(targets) {
  const failed = [];
  const stale = [];
  for (const { extName, buildScript, distFile } of targets) {
    const before = existsSync(distFile) ? readFileSync(distFile) : null;
    const backupDir = mkdtempSync(path.join(tmpdir(), "nodvard-deck-ext-bundle-check-"));
    const backupFile = path.join(backupDir, "index.js");
    if (before !== null) writeFileSync(backupFile, before);

    try {
      runBuild(buildScript);
    } catch (err) {
      process.stderr.write(`FEHLER beim Bauen von "${extName}":\n${err.stderr?.toString() ?? err.message}\n`);
      failed.push(extName);
      if (before !== null) copyFileSync(backupFile, distFile);
      rmSync(backupDir, { recursive: true, force: true });
      continue;
    }

    const after = existsSync(distFile) ? readFileSync(distFile) : null;
    // `core.autocrlf=true` (dieses Repo) laesst den ausgecheckten Stand mit CRLF auf
    // der Platte liegen, waehrend esbuild (plattformunabhaengig) immer LF schreibt --
    // ohne Normalisierung waere JEDES committete Bundle "veraltet", nur weil es zuletzt
    // ueber `git checkout` statt einen frischen Build auf die Platte kam.
    const normalize = (buf) => (buf === null ? null : buf.toString("utf-8").replace(/\r\n/g, "\n"));
    const matches = before !== null && after !== null && normalize(before) === normalize(after);

    // Committetes Bundle NIE stehen lassen, wie es der frische Build hinterlassen
    // hat -- --check darf das Arbeitsverzeichnis nicht veraendern.
    if (before !== null) copyFileSync(backupFile, distFile);
    else if (after !== null) rmSync(distFile, { force: true });
    rmSync(backupDir, { recursive: true, force: true });

    if (before === null) {
      process.stderr.write(`FEHLT: "extensions/${extName}/frontend/dist/index.js" ist nicht committet.\n`);
      stale.push(extName);
    } else if (!matches) {
      process.stderr.write(
        `VERALTET: "extensions/${extName}/frontend/dist/index.js" weicht von einem frischen Build ab -- ` +
          `"node extensions/${extName}/frontend/build.mjs" laufen lassen und das Ergebnis committen.\n`,
      );
      stale.push(extName);
    } else {
      process.stdout.write(`OK: ${extName}\n`);
    }
  }
  return { failed, stale };
}

const targets = findBuildScripts();
if (targets.length === 0) {
  console.log("Keine Extension-Frontend-Bundles gefunden.");
  process.exit(0);
}

if (!checkOnly) {
  const failed = buildAll(targets);
  if (failed.length > 0) {
    console.error(`\n${failed.length} von ${targets.length} Extension-Bundle(s) fehlgeschlagen: ${failed.join(", ")}`);
    process.exit(1);
  }
  console.log(`\nOK: alle ${targets.length} Extension-Bundles gebaut.`);
  process.exit(0);
}

const { failed, stale } = checkAll(targets);
if (failed.length > 0 || stale.length > 0) {
  const parts = [];
  if (failed.length > 0) parts.push(`${failed.length} Build-Fehler (${failed.join(", ")})`);
  if (stale.length > 0) parts.push(`${stale.length} veraltet/fehlend (${stale.join(", ")})`);
  console.error(`\nFEHLGESCHLAGEN: ${parts.join("; ")}`);
  process.exit(1);
}
console.log(`\nOK: alle ${targets.length} Extension-Bundles aktuell.`);