Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  bundle-a2ui.mjs   Sprache: unbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

#!/usr/bin/env node

import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { resolvePnpmRunner } from "./pnpm-runner.mjs";

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const hashFile = path.join(rootDir, "src", "canvas-host", "a2ui", ".bundle.hash");
const outputFile = path.join(rootDir, "src", "canvas-host", "a2ui", "a2ui.bundle.js");
const a2uiRendererDir = path.join(rootDir, "vendor", "a2ui", "renderers", "lit");
const a2uiAppDir = path.join(rootDir, "apps", "shared", "OpenClawKit", "Tools", "CanvasA2UI");
const uiPackageFile = path.join(rootDir, "ui", "package.json");
const repoInputPaths = [uiPackageFile, a2uiRendererDir, a2uiAppDir];
const ignoredBundleHashInputPrefixes = ["vendor/a2ui/renderers/lit/dist"];
const relativeRepoInputPaths = repoInputPaths.map((inputPath) =>
  normalizePath(path.relative(rootDir, inputPath)),
);

function fail(message) {
  console.error(message);
  console.error("A2UI bundling failed. Re-run with: pnpm canvas:a2ui:bundle");
  console.error("If this persists, verify pnpm deps and try again.");
  process.exit(1);
}

async function pathExists(targetPath) {
  try {
    await fs.stat(targetPath);
    return true;
  } catch {
    return false;
  }
}

function normalizePath(filePath) {
  return filePath.split(path.sep).join("/");
}

export function isBundleHashInputPath(filePath, repoRoot = rootDir) {
  const relativePath = normalizePath(path.relative(repoRoot, filePath));
  return !ignoredBundleHashInputPrefixes.some(
    (ignoredPath) => relativePath === ignoredPath || relativePath.startsWith(`${ignoredPath}/`),
  );
}

export function getLocalRolldownCliCandidates(repoRoot = rootDir) {
  return [
    path.join(repoRoot, "node_modules", "rolldown", "bin", "cli.mjs"),
    path.join(repoRoot, "node_modules", ".pnpm", "node_modules", "rolldown", "bin", "cli.mjs"),
    path.join(
      repoRoot,
      "node_modules",
      ".pnpm",
      "rolldown@1.0.0-rc.12",
      "node_modules",
      "rolldown",
      "bin",
      "cli.mjs",
    ),
  ];
}

export function getBundleHashRepoInputPaths(repoRoot = rootDir) {
  return [
    path.join(repoRoot, "ui", "package.json"),
    path.join(repoRoot, "vendor", "a2ui", "renderers", "lit"),
    path.join(repoRoot, "apps", "shared", "OpenClawKit", "Tools", "CanvasA2UI"),
  ];
}

export function getBundleHashInputPaths(repoRoot = rootDir) {
  return getBundleHashRepoInputPaths(repoRoot);
}

export function compareNormalizedPaths(left, right) {
  const normalizedLeft = normalizePath(left);
  const normalizedRight = normalizePath(right);
  if (normalizedLeft < normalizedRight) {
    return -1;
  }
  if (normalizedLeft > normalizedRight) {
    return 1;
  }
  return 0;
}

async function walkFiles(entryPath, files) {
  if (!isBundleHashInputPath(entryPath)) {
    return;
  }
  const stat = await fs.stat(entryPath);
  if (!stat.isDirectory()) {
    files.push(entryPath);
    return;
  }
  const entries = await fs.readdir(entryPath);
  for (const entry of entries) {
    await walkFiles(path.join(entryPath, entry), files);
  }
}

function listTrackedInputFiles() {
  const result = spawnSync("git", ["ls-files", "--", ...relativeRepoInputPaths], {
    cwd: rootDir,
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
  });
  if (result.status !== 0) {
    return null;
  }
  const trackedFiles = result.stdout
    .split("\n")
    .filter(Boolean)
    .map((filePath) => path.join(rootDir, filePath))
    .filter((filePath) => isBundleHashInputPath(filePath));
  return trackedFiles;
}

async function computeHash() {
  let files = listTrackedInputFiles();
  if (!files) {
    files = [];
    for (const inputPath of getBundleHashRepoInputPaths(rootDir)) {
      await walkFiles(inputPath, files);
    }
  }
  files = [...new Set(files)].toSorted(compareNormalizedPaths);

  const hash = createHash("sha256");
  for (const filePath of files) {
    hash.update(normalizePath(path.relative(rootDir, filePath)));
    hash.update("\0");
    hash.update(await fs.readFile(filePath));
    hash.update("\0");
  }
  return hash.digest("hex");
}

function runStep(command, args, options = {}) {
  const result = spawnSync(command, args, {
    cwd: rootDir,
    stdio: "inherit",
    env: process.env,
    ...options,
  });
  if (result.status !== 0) {
    process.exit(result.status ?? 1);
  }
}

function runPnpm(pnpmArgs) {
  const runner = resolvePnpmRunner({
    pnpmArgs,
    nodeExecPath: process.execPath,
    npmExecPath: process.env.npm_execpath,
    comSpec: process.env.ComSpec,
    platform: process.platform,
  });
  runStep(runner.command, runner.args, {
    shell: runner.shell,
    windowsVerbatimArguments: runner.windowsVerbatimArguments,
  });
}

async function main() {
  const hasRendererDir = await pathExists(a2uiRendererDir);
  const hasAppDir = await pathExists(a2uiAppDir);
  const hasOutputFile = await pathExists(outputFile);
  if (!hasRendererDir || !hasAppDir) {
    if (hasOutputFile) {
      console.log("A2UI sources missing; keeping prebuilt bundle.");
      return;
    }
    if (process.env.OPENCLAW_SPARSE_PROFILE || process.env.OPENCLAW_A2UI_SKIP_MISSING === "1") {
      console.error(
        "A2UI sources missing; skipping bundle because OPENCLAW_A2UI_SKIP_MISSING=1 or OPENCLAW_SPARSE_PROFILE is set.",
      );
      return;
    }
    fail(`A2UI sources missing and no prebuilt bundle found at: ${outputFile}`);
  }

  const currentHash = await computeHash();
  if (await pathExists(hashFile)) {
    const previousHash = (await fs.readFile(hashFile, "utf8")).trim();
    if (previousHash === currentHash && hasOutputFile) {
      console.log("A2UI bundle up to date; skipping.");
      return;
    }
  }

  runPnpm(["-s", "exec", "tsgo", "-p", path.join(a2uiRendererDir, "tsconfig.json")]);

  const localRolldownCliCandidates = getLocalRolldownCliCandidates(rootDir);
  const localRolldownCli = (
    await Promise.all(
      localRolldownCliCandidates.map(async (candidate) =>
        (await pathExists(candidate)) ? candidate : null,
      ),
    )
  ).find(Boolean);

  if (localRolldownCli) {
    runStep(process.execPath, [
      localRolldownCli,
      "-c",
      path.join(a2uiAppDir, "rolldown.config.mjs"),
    ]);
  } else {
    runPnpm(["-s", "exec", "rolldown", "-c", path.join(a2uiAppDir, "rolldown.config.mjs")]);
  }

  await fs.writeFile(hashFile, `${currentHash}\n`, "utf8");
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  await main().catch((error) => {
    fail(error instanceof Error ? error.message : String(error));
  });
}

[Dauer der Verarbeitung: 0.4 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge