diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd647d6e3..885d87fcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: command: pnpm lint - runtime: node task: test - command: pnpm test + command: pnpm canvas:a2ui:bundle && pnpm test - runtime: node task: build command: pnpm build @@ -88,7 +88,7 @@ jobs: command: pnpm format - runtime: bun task: test - command: bunx vitest run + command: pnpm canvas:a2ui:bundle && bunx vitest run - runtime: bun task: build command: bunx tsc -p tsconfig.json @@ -188,6 +188,7 @@ jobs: runs-on: blacksmith-4vcpu-windows-2025 env: NODE_OPTIONS: --max-old-space-size=4096 + CLAWDBOT_TEST_WORKERS: 1 defaults: run: shell: bash @@ -200,7 +201,7 @@ jobs: command: pnpm lint - runtime: node task: test - command: pnpm test + command: pnpm canvas:a2ui:bundle && pnpm test - runtime: node task: build command: pnpm build @@ -277,8 +278,6 @@ jobs: checks-macos: if: github.event_name == 'pull_request' runs-on: macos-latest - env: - NODE_OPTIONS: --max-old-space-size=4096 strategy: fail-fast: false matrix: @@ -344,6 +343,8 @@ jobs: pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true - name: Run ${{ matrix.task }} + env: + NODE_OPTIONS: --max-old-space-size=4096 run: ${{ matrix.command }} macos-app: diff --git a/scripts/bundle-a2ui.sh b/scripts/bundle-a2ui.sh index b0bcce221..393685830 100755 --- a/scripts/bundle-a2ui.sh +++ b/scripts/bundle-a2ui.sh @@ -1,55 +1,87 @@ #!/usr/bin/env bash set -euo pipefail +on_error() { + echo "A2UI bundling failed. Re-run with: pnpm canvas:a2ui:bundle" >&2 + echo "If this persists, verify pnpm deps and try again." >&2 +} +trap on_error ERR + ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash" +OUTPUT_FILE="$ROOT_DIR/src/canvas-host/a2ui/a2ui.bundle.js" +A2UI_RENDERER_DIR="$ROOT_DIR/vendor/a2ui/renderers/lit" +A2UI_APP_DIR="$ROOT_DIR/apps/shared/OpenClawKit/Tools/CanvasA2UI" + +# Docker builds exclude vendor/apps via .dockerignore. +# In that environment we must keep the prebuilt bundle. +if [[ ! -d "$A2UI_RENDERER_DIR" || ! -d "$A2UI_APP_DIR" ]]; then + echo "A2UI sources missing; keeping prebuilt bundle." + exit 0 +fi INPUT_PATHS=( "$ROOT_DIR/package.json" "$ROOT_DIR/pnpm-lock.yaml" - "$ROOT_DIR/vendor/a2ui/renderers/lit" - "$ROOT_DIR/apps/shared/ClawdbotKit/Tools/CanvasA2UI" + "$A2UI_RENDERER_DIR" + "$A2UI_APP_DIR" ) -collect_files() { - local path - for path in "${INPUT_PATHS[@]}"; do - if [[ -d "$path" ]]; then - find "$path" -type f -print0 - else - printf '%s\0' "$path" - fi - done +compute_hash() { + ROOT_DIR="$ROOT_DIR" node --input-type=module - "${INPUT_PATHS[@]}" <<'NODE' +import { createHash } from "node:crypto"; +import { promises as fs } from "node:fs"; +import path from "node:path"; + +const rootDir = process.env.ROOT_DIR ?? process.cwd(); +const inputs = process.argv.slice(2); +const files = []; + +async function walk(entryPath) { + const st = await fs.stat(entryPath); + if (st.isDirectory()) { + const entries = await fs.readdir(entryPath); + for (const entry of entries) { + await walk(path.join(entryPath, entry)); + } + return; + } + files.push(entryPath); } -compute_hash() { - # Use sha256sum on Linux/Windows (Git Bash), shasum on macOS - local sha_cmd - if command -v sha256sum &>/dev/null; then - sha_cmd="sha256sum" - elif command -v shasum &>/dev/null; then - sha_cmd="shasum -a 256" - else - echo "No sha256 tool found (sha256sum or shasum)" >&2 - exit 1 - fi - collect_files \ - | LC_ALL=C sort -z \ - | xargs -0 $sha_cmd \ - | $sha_cmd \ - | awk '{print $1}' +for (const input of inputs) { + await walk(input); +} + +function normalize(p) { + return p.split(path.sep).join("/"); +} + +files.sort((a, b) => normalize(a).localeCompare(normalize(b))); + +const hash = createHash("sha256"); +for (const filePath of files) { + const rel = normalize(path.relative(rootDir, filePath)); + hash.update(rel); + hash.update("\0"); + hash.update(await fs.readFile(filePath)); + hash.update("\0"); +} + +process.stdout.write(hash.digest("hex")); +NODE } current_hash="$(compute_hash)" if [[ -f "$HASH_FILE" ]]; then previous_hash="$(cat "$HASH_FILE")" - if [[ "$previous_hash" == "$current_hash" ]]; then + if [[ "$previous_hash" == "$current_hash" && -f "$OUTPUT_FILE" ]]; then echo "A2UI bundle up to date; skipping." exit 0 fi fi -pnpm -s exec tsc -p vendor/a2ui/renderers/lit/tsconfig.json -rolldown -c apps/shared/ClawdbotKit/Tools/CanvasA2UI/rolldown.config.mjs +pnpm -s exec tsc -p "$A2UI_RENDERER_DIR/tsconfig.json" +rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs" echo "$current_hash" > "$HASH_FILE"