From db8f3ae83020e8aad90ecdc24184cfba4784f6ff Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:03:25 -0700 Subject: [PATCH] test(ui): guard the Cloudflare build's self-heal step against regression Closes #6642. A CI job cannot actually reproduce Cloudflare Pages' scoped-install behavior to regression-test #6624 end to end -- confirmed empirically that a plain `npm ci` run from apps/loopover-ui in a normal git checkout correctly finds and installs the full workspace tree (Cloudflare's build sandbox does something else that isn't reproducible via plain npm/git commands in GitHub Actions). The reliable, cheap guard is the invariant that actually prevents the regression: build:cloudflare must keep running a full monorepo-root `npm ci` before `ui:build`. Adds a unit test asserting that exact script shape, mirroring the existing ci-ui-build-openapi.test.ts convention of testing critical build-recipe content directly. Verified it fails when the self-heal step is removed (reverted locally, confirmed red, restored). --- .../ui-cloudflare-build-self-heal.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/unit/ui-cloudflare-build-self-heal.test.ts diff --git a/test/unit/ui-cloudflare-build-self-heal.test.ts b/test/unit/ui-cloudflare-build-self-heal.test.ts new file mode 100644 index 0000000000..f0694fff05 --- /dev/null +++ b/test/unit/ui-cloudflare-build-self-heal.test.ts @@ -0,0 +1,29 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const read = (path: string) => JSON.parse(readFileSync(path, "utf8")); + +// #6642: the Cloudflare Pages project for loopover-ui has its root directory set to apps/loopover-ui, so +// Cloudflare's own automatic `npm clean-install` step only installs THIS workspace's own declared +// dependencies -- it never reaches sibling workspace packages (proved live: it silently under-installed +// until packages/loopover-engine gained @anthropic-ai/claude-agent-sdk/web-tree-sitter, then every PR +// touching apps/loopover-ui/** started failing `Workers Builds: loopover-ui` with TS2307). #6624 fixed +// this by making build:cloudflare self-sufficient: it runs a full monorepo-root `npm ci` itself before +// building, so it no longer depends on what Cloudflare's own scoped install covered. +// +// A CI job cannot actually reproduce Cloudflare's scoped-install behavior to regression-test this end to +// end -- confirmed empirically that a plain `npm ci` run from apps/loopover-ui in a normal git checkout +// correctly finds and installs the FULL workspace tree (Cloudflare's specific build sandbox does +// something else, not reproducible via plain npm/git commands). The reliable, cheap guard is asserting +// the self-heal step itself is still present -- this is what actually prevents the regression from +// recurring, regardless of whether the original failure mode can be simulated in CI. +describe("apps/loopover-ui's Cloudflare build stays self-sufficient (#6642)", () => { + it("build:cloudflare runs a full monorepo-root npm ci before building, not just the build script alone", () => { + const pkg = read("apps/loopover-ui/package.json"); + const script = pkg.scripts["build:cloudflare"]; + expect(script).toBeTypeOf("string"); + // Order matters: the root install must happen BEFORE ui:build, so build:cloudflare is + // self-sufficient regardless of what Cloudflare's own root-scoped install step already did. + expect(script).toMatch(/^npm --prefix \.\.\/\.\. ci\s*&&\s*npm --prefix \.\.\/\.\. run ui:build$/); + }); +});