build: stabilize a2ui bundle inputs

This commit is contained in:
Peter Steinberger
2026-04-20 17:24:01 +01:00
parent 18021818ce
commit 6e58da9750
3 changed files with 19 additions and 49 deletions

View File

@@ -2,7 +2,6 @@
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
@@ -13,9 +12,10 @@ 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 rootPackageFile = path.join(rootDir, "package.json");
const pnpmLockFile = path.join(rootDir, "pnpm-lock.yaml");
const uiPackageFile = path.join(rootDir, "ui", "package.json");
const bundleDependencyIds = ["lit", "@lit/context", "@lit-labs/signals", "signal-utils"];
const repoInputPaths = [uiPackageFile, a2uiRendererDir, a2uiAppDir];
const repoInputPaths = [rootPackageFile, pnpmLockFile, uiPackageFile, a2uiRendererDir, a2uiAppDir];
const ignoredBundleHashInputPrefixes = ["vendor/a2ui/renderers/lit/dist"];
const relativeRepoInputPaths = repoInputPaths.map((inputPath) =>
normalizePath(path.relative(rootDir, inputPath)),
@@ -67,34 +67,16 @@ export function getLocalRolldownCliCandidates(repoRoot = rootDir) {
export function getBundleHashRepoInputPaths(repoRoot = rootDir) {
return [
path.join(repoRoot, "package.json"),
path.join(repoRoot, "pnpm-lock.yaml"),
path.join(repoRoot, "ui", "package.json"),
path.join(repoRoot, "vendor", "a2ui", "renderers", "lit"),
path.join(repoRoot, "apps", "shared", "OpenClawKit", "Tools", "CanvasA2UI"),
];
}
export function getResolvedBundleDependencyPackageJsonPaths(repoRoot = rootDir) {
const uiNodeModules = path.join(repoRoot, "ui", "node_modules");
const repoNodeModules = path.join(repoRoot, "node_modules");
const paths = [];
for (const dependencyId of bundleDependencyIds) {
const candidates = [
path.join(uiNodeModules, dependencyId, "package.json"),
path.join(repoNodeModules, dependencyId, "package.json"),
];
const match = candidates.find((candidate) => existsSync(candidate));
if (match) {
paths.push(match);
}
}
return [...new Set(paths)];
}
export function getBundleHashInputPaths(repoRoot = rootDir) {
return [
...getBundleHashRepoInputPaths(repoRoot),
...getResolvedBundleDependencyPackageJsonPaths(repoRoot),
];
return getBundleHashRepoInputPaths(repoRoot);
}
export function compareNormalizedPaths(left, right) {
@@ -138,7 +120,7 @@ function listTrackedInputFiles() {
.filter(Boolean)
.map((filePath) => path.join(rootDir, filePath))
.filter((filePath) => isBundleHashInputPath(filePath));
return [...trackedFiles, ...getResolvedBundleDependencyPackageJsonPaths(rootDir)];
return trackedFiles;
}
async function computeHash() {
@@ -148,7 +130,6 @@ async function computeHash() {
for (const inputPath of getBundleHashRepoInputPaths(rootDir)) {
await walkFiles(inputPath, files);
}
files.push(...getResolvedBundleDependencyPackageJsonPaths(rootDir));
}
files = [...new Set(files)].toSorted(compareNormalizedPaths);

View File

@@ -1 +1 @@
7d425e150815426138e00903b7553c28742a2f696ff7f608ee0047f21b2f7fe5
445aaac0723949e8cee66e9bfa82ba789531c2cf3990e74a240939bc49e5933f

View File

@@ -5,7 +5,6 @@ import {
getBundleHashInputPaths,
getBundleHashRepoInputPaths,
getLocalRolldownCliCandidates,
getResolvedBundleDependencyPackageJsonPaths,
isBundleHashInputPath,
} from "../../scripts/bundle-a2ui.mjs";
@@ -52,34 +51,24 @@ describe("scripts/bundle-a2ui.mjs", () => {
]);
});
it("keeps repo-root package churn out of bundle hash inputs", () => {
it("tracks repo dependency manifests through lockfile inputs", () => {
const repoRoot = path.resolve("repo-root");
const inputPaths = getBundleHashRepoInputPaths(repoRoot);
expect(inputPaths).toContain(path.join(repoRoot, "package.json"));
expect(inputPaths).toContain(path.join(repoRoot, "pnpm-lock.yaml"));
expect(inputPaths).toContain(path.join(repoRoot, "ui", "package.json"));
expect(inputPaths).not.toContain(path.join(repoRoot, "package.json"));
expect(inputPaths).not.toContain(path.join(repoRoot, "pnpm-lock.yaml"));
});
it("tracks only the resolved bundle dependency manifests from node_modules", () => {
it("keeps local node_modules state out of bundle hash inputs", () => {
const repoRoot = process.cwd();
const dependencyPaths = getResolvedBundleDependencyPackageJsonPaths(repoRoot);
const relativeDependencyPaths = dependencyPaths.map((dependencyPath) =>
path.relative(repoRoot, dependencyPath).replaceAll(path.sep, "/"),
);
const inputPaths = getBundleHashInputPaths(repoRoot);
expect(
relativeDependencyPaths.map((relativePath) => relativePath.replace(/^ui\//u, "")),
).toEqual([
path.posix.join("node_modules", "lit", "package.json"),
path.posix.join("node_modules", "@lit/context", "package.json"),
path.posix.join("node_modules", "@lit-labs/signals", "package.json"),
path.posix.join("node_modules", "signal-utils", "package.json"),
]);
expect(
relativeDependencyPaths.every((relativePath) => /^(ui\/)?node_modules\//u.test(relativePath)),
).toBe(true);
expect(getBundleHashInputPaths(repoRoot)).not.toContain(path.join(repoRoot, "package.json"));
expect(getBundleHashInputPaths(repoRoot)).not.toContain(path.join(repoRoot, "pnpm-lock.yaml"));
expect(inputPaths).toContain(path.join(repoRoot, "package.json"));
expect(inputPaths).toContain(path.join(repoRoot, "pnpm-lock.yaml"));
expect(inputPaths).not.toContain(path.join(repoRoot, "node_modules", "lit", "package.json"));
expect(inputPaths).not.toContain(
path.join(repoRoot, "ui", "node_modules", "lit", "package.json"),
);
});
});