From 5ed901691421052e214815138253d0738aa96266 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 15 Apr 2026 00:44:03 +0100 Subject: [PATCH] fix: narrow a2ui bundle hash inputs --- scripts/bundle-a2ui.mjs | 54 ++++++++++++++++++++++++------- src/canvas-host/a2ui/.bundle.hash | 2 +- test/scripts/bundle-a2ui.test.ts | 30 +++++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/scripts/bundle-a2ui.mjs b/scripts/bundle-a2ui.mjs index 3cf462e4c3b..096a56f3034 100644 --- a/scripts/bundle-a2ui.mjs +++ b/scripts/bundle-a2ui.mjs @@ -2,6 +2,7 @@ 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"; @@ -12,14 +13,11 @@ 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 inputPaths = [ - path.join(rootDir, "package.json"), - path.join(rootDir, "pnpm-lock.yaml"), - a2uiRendererDir, - a2uiAppDir, -]; +const uiPackageFile = path.join(rootDir, "ui", "package.json"); +const bundleDependencyIds = ["lit", "@lit/context", "@lit-labs/signals", "signal-utils"]; +const repoInputPaths = [uiPackageFile, a2uiRendererDir, a2uiAppDir]; const ignoredBundleHashInputPrefixes = ["vendor/a2ui/renderers/lit/dist"]; -const relativeInputPaths = inputPaths.map((inputPath) => +const relativeRepoInputPaths = repoInputPaths.map((inputPath) => normalizePath(path.relative(rootDir, inputPath)), ); @@ -67,6 +65,38 @@ export function getLocalRolldownCliCandidates(repoRoot = rootDir) { ]; } +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 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), + ]; +} + export function compareNormalizedPaths(left, right) { const normalizedLeft = normalizePath(left); const normalizedRight = normalizePath(right); @@ -95,7 +125,7 @@ async function walkFiles(entryPath, files) { } function listTrackedInputFiles() { - const result = spawnSync("git", ["ls-files", "--", ...relativeInputPaths], { + const result = spawnSync("git", ["ls-files", "--", ...relativeRepoInputPaths], { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], @@ -103,22 +133,24 @@ function listTrackedInputFiles() { if (result.status !== 0) { return null; } - return result.stdout + const trackedFiles = result.stdout .split("\n") .filter(Boolean) .map((filePath) => path.join(rootDir, filePath)) .filter((filePath) => isBundleHashInputPath(filePath)); + return [...trackedFiles, ...getResolvedBundleDependencyPackageJsonPaths(rootDir)]; } async function computeHash() { let files = listTrackedInputFiles(); if (!files) { files = []; - for (const inputPath of inputPaths) { + for (const inputPath of getBundleHashRepoInputPaths(rootDir)) { await walkFiles(inputPath, files); } + files.push(...getResolvedBundleDependencyPackageJsonPaths(rootDir)); } - files.sort(compareNormalizedPaths); + files = [...new Set(files)].toSorted(compareNormalizedPaths); const hash = createHash("sha256"); for (const filePath of files) { diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index 5c8fac98337..2f4aa11b9d6 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -7292a1097ab7bfbec0542b79eb965165d4c73870063e8137ba7da40d767642d2 +0206b912cd65c0fcdb7a0ac3aa9a536ab01378220b4eb314fc85acdf82ac16fe diff --git a/test/scripts/bundle-a2ui.test.ts b/test/scripts/bundle-a2ui.test.ts index 1143b2e2e1e..da658478980 100644 --- a/test/scripts/bundle-a2ui.test.ts +++ b/test/scripts/bundle-a2ui.test.ts @@ -2,7 +2,10 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { compareNormalizedPaths, + getBundleHashInputPaths, + getBundleHashRepoInputPaths, getLocalRolldownCliCandidates, + getResolvedBundleDependencyPackageJsonPaths, isBundleHashInputPath, } from "../../scripts/bundle-a2ui.mjs"; @@ -48,4 +51,31 @@ describe("scripts/bundle-a2ui.mjs", () => { "repo/รค.ts", ]); }); + + it("keeps repo-root package churn out of bundle hash inputs", () => { + const repoRoot = path.resolve("repo-root"); + const inputPaths = getBundleHashRepoInputPaths(repoRoot); + + 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", () => { + const repoRoot = process.cwd(); + const dependencyPaths = getResolvedBundleDependencyPackageJsonPaths(repoRoot); + + expect(dependencyPaths).toContain(path.join(repoRoot, "node_modules", "lit", "package.json")); + expect(dependencyPaths).toContain( + path.join(repoRoot, "node_modules", "@lit/context", "package.json"), + ); + expect(dependencyPaths).toContain( + path.join(repoRoot, "node_modules", "@lit-labs/signals", "package.json"), + ); + expect(dependencyPaths).toContain( + path.join(repoRoot, "node_modules", "signal-utils", "package.json"), + ); + expect(getBundleHashInputPaths(repoRoot)).not.toContain(path.join(repoRoot, "package.json")); + expect(getBundleHashInputPaths(repoRoot)).not.toContain(path.join(repoRoot, "pnpm-lock.yaml")); + }); });