Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions content/docs/guide/ci-cd-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 43 additions & 12 deletions e2e/live/ci/start-backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
Expand Down Expand Up @@ -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"

Expand All @@ -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"
Expand Down
Loading