diff --git a/content/docs/guide/ci-cd-pipeline.md b/content/docs/guide/ci-cd-pipeline.md index 8093708281..5c1ff015f6 100644 --- a/content/docs/guide/ci-cd-pipeline.md +++ b/content/docs/guide/ci-cd-pipeline.md @@ -562,9 +562,24 @@ a second key, `OBJECTSTACK_REF`, moved by hand under a MUST that nothing could c shape as the version drift above, and retired for the same reason (objectui#7964). Deriving it means the app source and the published packages it runs on come from one release by construction. -No transitive dependency is overridden in the backend's install: the published manifests decide -what installs, which is the pair the lane exists to test. The file used to carry a **second** -pin, `BETTER_AUTH_VERSION`, with `e2e/live/ci/better-auth-pin.mjs` as its guard. It was a +The published manifests decide what the backend's install puts on disk, which is the pair the +lane exists to test. **One** transitive override is written, and only because npm 10 cannot +install the published graph without it: `start-backend.sh` adds a root `overrides.vitest` +(its `VITEST_OVERRIDE`) to the rewritten showcase manifest. Without it npm 10 — the npm Node 22 +ships, so the npm the runner installs with — dies building the ideal tree with +`Cannot read properties of null (reading 'edgesOut')` on an optional `vitest` peer in the +published graph ([#12649](https://github.com/objectstack-ai/objectui/issues/12649)). Nothing +installed vitest at `18.0.0-next.2`, so there the override steered the resolver and changed no +package the backend runs — a one-off reading on that issue, which nothing here re-derives. +The script carries the reason and the condition for removing it, and its stamp carries the +value, so a cached fixture prepared under another install recipe is rebuilt instead of reused +(the fixture cache key hashes `backend.env` alone, so it cannot see a recipe change made in the +script). ⚠️ What the cache still hides: a cold install broken by the registry moving under +unchanged pins — which is how this one broke — stays out of sight until `OBJECTSTACK_VERSION` +moves, because only then does the key miss. + +`backend.env` used to carry a **second** pin, `BETTER_AUTH_VERSION`, with +`e2e/live/ci/better-auth-pin.mjs` as its guard. It was a workaround for a break inside the published packages: `@objectstack/plugin-auth` imported an export `@better-auth/core@1.7.3` removed in a patch release, so the lane held the family at 1.7.2 (objectstack#16186, objectui#8084). Both were retired together in the 17.5.0 bump, as their own diff --git a/e2e/live/ci/start-backend.sh b/e2e/live/ci/start-backend.sh index d8eac54152..547ec18abf 100755 --- a/e2e/live/ci/start-backend.sh +++ b/e2e/live/ci/start-backend.sh @@ -10,10 +10,11 @@ # 2. Rewrite its package.json: every `@objectstack/*` workspace dep -> the # pinned published OBJECTSTACK_VERSION; dev-only tooling dropped. # 3. `npm install` (published tarballs only — nothing is built from source, -# so the lane tests the console against the released artifacts). No -# transitive dependency is overridden: the published manifests decide -# what installs, which is the pair this lane exists to test (the -# `better-auth` pin retired with the 17.5.0 bump — see backend.env). +# so the lane tests the console against the released artifacts). The +# published manifests decide what installs, which is the pair this lane +# exists to test, with ONE exception: the root `overrides.vitest` that +# npm 10 needs to install the published graph at all (see VITEST_OVERRIDE +# below for why, and for when it goes). # 4. `objectstack dev --seed-admin --fresh` on $LIVE_BACKEND_PORT with a # throwaway sqlite db, then poll the seeded sign-in until it answers. # @@ -35,6 +36,33 @@ REPO_URL="${OBJECTSTACK_REPO_URL:-https://github.com/objectstack-ai/objectstack. APP_DIR="$BACKEND_DIR/app" STAMP="$BACKEND_DIR/.prepared" +# The one transitive override the install writes, as a root `overrides` entry in +# the rewritten manifest (objectui#12649). Without it, npm 10 (the npm Node 22 +# ships, and so the npm this lane's runner installs with) cannot install the +# published @objectstack/cli graph: it dies building the ideal tree with +# `Cannot read properties of null (reading 'edgesOut')`. The graph reaches +# `vitest` only as an OPTIONAL peer (at 18.0.0-next.2: `better-auth`'s, under +# plugin-auth, and `@objectstack/metadata-core`'s). The newest vitest 4 peers +# `@vitest/browser-webdriverio ^4.1.11`, whose only 4.x match peers back on an +# older vitest exactly, and npm 10's peer-set walk crashes on that set instead +# of resolving it. Holding vitest at 4.0.x gives it a coherent set to resolve. +# Nothing installs vitest, so the override steers the resolver and changes no +# package the backend runs: measured once, at 18.0.0-next.2, the tree npm 10 +# installs with it was the tree npm 11 installs without it, path for path and +# version for version — a one-off reading that nothing here re-derives. +# It is the remedy objectstack documents for this crash (its troubleshooting +# entry "Cannot read properties of null (reading 'edgesOut') from npm install"). +# The value is part of the stamp below, so a fixture prepared under another +# recipe — the Actions cache restores one without asking — is rebuilt, never +# reused: the cold install cannot hide behind a cache hit. +# ⛔ Remove it (this line, the `overrides` line in prepare(), and the stamp's +# third component) once the published `@objectstack/cli` at OBJECTSTACK_VERSION +# installs on npm 10 with no override. Re-measure on every pin bump: drop the +# `overrides` line, run this script under npm 10 with a FRESH LIVE_BACKEND_DIR +# and a fresh npm cache, and read the install's exit. A re-run in a prepared dir +# proves nothing, because an install from its package-lock.json never crashes. +VITEST_OVERRIDE='~4.0.0' + # The showcase-app commit is DERIVED here, and is declared nowhere: it is # whatever the `@objectstack/cli@$OBJECTSTACK_VERSION` release tag points at, so # the metadata checked out below and the published packages installed on top of @@ -80,10 +108,11 @@ echo "[live-backend] resolved $OBJECTSTACK_TAG -> $OBJECTSTACK_REF" # installed — and a re-pointed tag is exactly such a change with no key of its # own to hash. The workflow's fixture cache key is the hash of backend.env, # which carries the version for the same reason; see the ⚠️ note there for the -# one path that key can no longer see. The stamp used to carry the retired -# `better-auth` pin as a third component, so a fixture prepared under that pin -# no longer matches and is rebuilt without the override. -WANT_STAMP="$OBJECTSTACK_REF $OBJECTSTACK_VERSION" +# one path that key can no longer see. The third component is the install +# recipe's one override: that key hashes backend.env only, so a recipe change +# made in this script restores the old fixture on a hit, and this stamp is what +# rejects it. (The retired `better-auth` pin used to sit in the same slot.) +WANT_STAMP="$OBJECTSTACK_REF $OBJECTSTACK_VERSION vitest@$VITEST_OVERRIDE" mkdir -p "$BACKEND_DIR" @@ -108,17 +137,19 @@ prepare() { # workspace:* -> the pinned published version; drop dev tooling the server # doesn't need (playwright/vitest/typescript — the CLI loads the TS config # itself). Keep non-@objectstack runtime deps (e.g. @modelcontextprotocol/sdk). - # No `overrides` block is written: the published @objectstack/* manifests - # decide their own transitive versions. + # The only `overrides` entry written is VITEST_OVERRIDE (see its note above); + # otherwise the published @objectstack/* manifests decide their own + # transitive versions. node -e ' const fs = require("fs"); - const file = process.argv[1], pin = process.argv[2]; + const file = process.argv[1], pin = process.argv[2], vitest = process.argv[3]; const pkg = JSON.parse(fs.readFileSync(file, "utf8")); for (const k of Object.keys(pkg.dependencies || {})) if (k.startsWith("@objectstack/")) pkg.dependencies[k] = pin; pkg.devDependencies = { "@objectstack/cli": pin }; + pkg.overrides = { ...pkg.overrides, vitest }; fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n"); - ' "$APP_DIR/package.json" "$OBJECTSTACK_VERSION" + ' "$APP_DIR/package.json" "$OBJECTSTACK_VERSION" "$VITEST_OVERRIDE" (cd "$APP_DIR" && npm install --no-audit --no-fund --loglevel=error) echo "$WANT_STAMP" > "$STAMP"