git:20261014.6ab8d5f to git:20261014.105149e

4 added, 0 removed. Audit A to A.

# CLI Journey E2E Instructions
These instructions apply to `e2e/journey/**`. Defer to the root
`AGENTS.md` for repository-wide rules.
## Purpose
This project protects the customer local setup journey across every supported
CLI framework. Tests must exercise a fresh app directory that runs the CLI local
runtime path (`doctor`, `start`, `setup --server local`) before starting the
generated app. It must not test the checked-in demo apps. The fresh directory
starts empty (setup scaffolds the skeleton, page posture) or seeded from
`fixtures/preexisting/<framework>` (`--preexisting-app`, widget posture per
ADR 044) — both shapes run the same CLI path and browser journeys.
## Maintenance Rules
- Keep generated apps and temporary registries outside the repo worktree.
- Use `npm` inside the generated app to match the documented user path.
- Use `moon run release:pack` when producing workspace tarballs; it stages the
server platform binaries, packs all public packages, and verifies resolved
package metadata.
- CI may pass prebuilt release snapshot tarballs to the journey runner with
`--tarballs-dir`; that path must skip rebuilding and still verify/publish the
provided tarballs through Verdaccio.
- Every option CI sets has a `JOURNEY_*` variable (`journeyArgsFromEnv` in
`scripts/run-options.mjs`): CI's turbo job passes them that way, because
turbo puts arguments after `--` into every task's cache key. A new option
CI needs gets a variable there too.
- CI runs all journey variants (fresh-app matrix, passkey preset,
pre-existing app, testkit, remote https) concurrently in a single `--ci`
invocation of the runner (the `e2e:journey-local` lane, run with every other
lane by the e2e step in `.github/workflows/ci.yml` through `e2e:all`), which
parallelizes them through its own concurrency pool so one process shares
one port allocator.
The tarballs come from the snapshot step, gated on `snapshot` (a journey
surface is affected or the e2e step runs, computed by
`scripts/ci-mode.mjs`), and the fresh-app framework set is collapsed per-PR
through `JOURNEY_MATRIX=single|full` (the `journey_matrix` output, using
`scripts/frameworks.mjs`). New variants belong in the runner's `--ci` set,
not as new workflow steps.
+ - `e2e:journey-local` never packs its own tarballs: the lanes beside it read
+ the dist folders that `moon run release:pack` wipes and rebuilds. The lane
+ sets `JOURNEY_TARBALLS_REQUIRED=1`, so the runner refuses to start without
+ `JOURNEY_TARBALLS_DIR`; by hand, `moon run e2e:journey -- --ci` packs its own.
- CI must install Zitadel packages from current workflow tarballs through the
temporary Verdaccio registry, not from public npm.
- CI restores Verdaccio's storage from an earlier run, so the registry serves
its cached npmjs metadata for an hour before asking npmjs again (`maxage` in
`scripts/local-registry.mjs`). Keep that well under two hours: `nuxi init`
downloads its starter template from GitHub, which has picked up a new nuxt
release two hours after it was published, and a packument cached longer
lacks the version the template asks for, failing the nuxt lanes until it
expires.
- CI must run `npx @zitadel/cli@alpha doctor --runtime binary`,
`start --runtime binary`, and
`setup --framework <next|nuxt|react|vue|angular|solid|svelte|qwik> --server local`
from the fresh app directory with `--non-interactive --json`.
- The remote https lane (`--remote`; in `--ci`, react with the passkey-first
preset) is the one exception to `--server local`: setup points `--server` at
`scripts/https-edge.mjs`, a local https edge for `zitadel.test` in front of
the same local runtime. Like Vercel's edge it completes TLS only for its own
name, routes by `Host` and overwrites `X-Forwarded-*`, so it catches a
generated dev proxy that only works over plain http. The CLI and the
generated app's dev server reach it through a preloaded resolver and
`NODE_EXTRA_CA_CERTS` (`remoteHostEnv`); the browser still only talks to the
app on `localhost`.
- Docker fallback coverage must opt in with `--runtime docker --image <tag>`.
- Pack and upload only the public packages — `PUBLIC_RELEASE_PACKAGES` in
`scripts/release-manifest.mjs` is the authoritative list, and
`verify-tarballs.mjs` enforces that both the journey registry and release
artifact dirs carry exactly that set. Private support packages must stay out
of the artifact set.
- Keep the generated app on `localhost` for browser tests. WebAuthn rejects IP
address relying-party IDs such as `127.0.0.1`.
- The pre-existing-app fixtures (`fixtures/preexisting/<framework>`) must stay
minimal but real: exactly detectable by the CLI (ADR 044's posture hinge),
bootable via `npm run dev`, and carrying a distinctive shell and homepage the
specs assert survive setup. Only route-based frameworks (Next, Nuxt) belong
here — the SPA families keep the page posture.
- The testkit suite (`moon run e2e:journey -- --suite testkit`) is the
customer-configuration proof for `@zitadel/testing`: it scaffolds one next
app, installs the kit from the journey registry, copies the checked-in
consumer suite from `fixtures/testkit/`, and runs it inside the app. It must
never set `ZITADEL_SERVER_BINARY` or `NEXTGEN_*` env — proving that the
published binary and its embedded UIs work unconfigured is the point.
- Keep each framework suite Playwright-serial and one-worker. Framework suites
may run in parallel only when each suite gets its own generated app directory,
npm cache/tmp directories, app port, Zitadel port, and backend runtime state.
- Reserve runner ports through the block allocator in `scripts/ports.mjs`,
never via a listen-on-zero probe: reserved ports are bound minutes later, and
ephemeral-range ports get taken as outbound source ports by the parallel
suites' npm traffic in the meantime (the why and the block layout are
documented in that file).
- Passkey coverage is required in CI, and rides the `passkey-first` preset lane:
the shipped `password-first` default flow offers no passkey action, so the
passkey journey is registered only for that preset.
`JOURNEY_ENABLE_PASSKEY=0` is only a local debugging escape hatch.
- Keep diagnostics focused. Upload logs, setup JSON, lockfiles, Playwright
traces/reports, and metadata; do not upload generated `node_modules` or
`.next` directories.
- Time every lane step that takes seconds with `timePhase` and list a new
phase in `PHASES` (`scripts/timings.mjs`): the lane × phase table the runner
prints at the end, also in CI's job summary, is how a slow lane is found.