Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions .changeset/fix-start-then-setup-scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@zitadel/cli": patch
"@zitadel/sdk-nuxt": patch
---

Allow fresh app scaffolding after `zitadel start` creates local runtime ignore files, and load Nuxt runtime config through the Nuxt virtual imports module.
9 changes: 5 additions & 4 deletions .github/instructions/consumer-journey.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ gate, not as a demo-app e2e suite.
- CI must consume the current workflow's GoReleaser image and npm package
tarballs. Do not replace this with public npm packages for Zitadel packages.
- The journey must exercise the customer local runtime flow through `npx`:
`doctor`, `start`, then `setup --framework next --server local` with
`doctor`, `start`, then
`setup --framework <next|nuxt|react|vue|angular> --server local` with
`--non-interactive --json`.
- Produce package artifacts with `corepack pnpm --dir <package> pack` and keep
tarball verification for required package presence plus unresolved
Expand All @@ -19,14 +20,14 @@ gate, not as a demo-app e2e suite.
design tokens must not be uploaded or published to Verdaccio.
- Keep Verdaccio proxying npmjs for third-party dependencies while publishing
Zitadel tarballs under both `alpha` and `latest`.
- Keep generated Next.js apps outside the repo and use `npm` inside the
generated app to match the documented consumer path.
- Keep generated apps outside the repo and use `npm` inside the generated app to
match the documented consumer path.
- Preserve the CLI setup JSON contract: `--non-interactive --json` must parse
from stdout and return `status: "ok"`.
- Browser tests should run serially with one worker, use `localhost` for
WebAuthn, and require passkey coverage in CI. `JOURNEY_ENABLE_PASSKEY=0` is
only a local debugging escape hatch.
- Failure artifacts should include Playwright output/report, doctor/start/setup
JSON and stderr, local runtime metadata/logs, metadata, generated app package
manifests, Verdaccio logs, and Next logs. Do not upload generated
manifests, Verdaccio logs, and generated app logs. Do not upload generated
`node_modules` or `.next`.
67 changes: 43 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,14 @@ jobs:
retention-days: 7

consumer-journey-e2e:
name: consumer-journey-e2e (${{ matrix.framework }})
runs-on: depot-ubuntu-24.04-4
timeout-minutes: 35
needs: [goreleaser-snapshot, npm-pack-smoke]
strategy:
fail-fast: false
matrix:
framework: [next, nuxt, react, vue, angular]
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -667,38 +672,52 @@ jobs:
docker image inspect -f '{{.Os}}/{{.Architecture}}' "$(cat goreleaser-dist/nextgen-image.tag)" | grep -q '^linux/amd64$'
echo "ZITADEL_LOCAL_IMAGE=$(cat goreleaser-dist/nextgen-image.tag)" >> "$GITHUB_ENV"

- name: Prepare fresh Next.js app
- name: Prepare fresh ${{ matrix.framework }} app
id: prepare-app
env:
JOURNEY_APP_URL: http://localhost:3000
JOURNEY_FRAMEWORK: ${{ matrix.framework }}
JOURNEY_REGISTRY_URL: http://127.0.0.1:4873
JOURNEY_WORK_DIR: ${{ runner.temp }}/zitadel-cli-journey
run: node apps/cli-journey-e2e/scripts/prepare-next-app.mjs
JOURNEY_WORK_DIR: ${{ runner.temp }}/zitadel-cli-journey-${{ matrix.framework }}
run: node apps/cli-journey-e2e/scripts/prepare-app.mjs

- name: Start generated Next.js app
- name: Start generated ${{ matrix.framework }} app
run: |
cd "$JOURNEY_APP_DIR"
npm run dev -- --hostname localhost --port 3000 > "$RUNNER_TEMP/next-app.log" 2>&1 &
echo "NEXT_APP_PID=$!" >> "$GITHUB_ENV"
APP_LOG="$RUNNER_TEMP/${{ matrix.framework }}-app.log"
case "${{ matrix.framework }}" in
next)
npm run dev -- --hostname localhost --port 3000 > "$APP_LOG" 2>&1 &
;;
*)
npm run dev -- --host localhost --port 3000 > "$APP_LOG" 2>&1 &
;;
esac
echo "JOURNEY_APP_PID=$!" >> "$GITHUB_ENV"
for _ in $(seq 1 90); do
if curl -fsS http://localhost:3000/login >/dev/null; then
exit 0
fi
sleep 1
done
echo "::error::timed out waiting for generated Next.js app"
cat "$RUNNER_TEMP/next-app.log"
echo "::error::timed out waiting for generated ${{ matrix.framework }} app"
cat "$APP_LOG"
exit 1

- name: Run consumer journey Playwright tests
env:
JOURNEY_FRAMEWORK: ${{ matrix.framework }}
JOURNEY_PLAYWRIGHT_OUTPUT_DIR: apps/cli-journey-e2e/test-output/playwright/${{ matrix.framework }}/output
JOURNEY_PLAYWRIGHT_REPORT_DIR: apps/cli-journey-e2e/test-output/playwright/${{ matrix.framework }}/report
run: >-
corepack pnpm --filter @zitadel/cli-journey-e2e
exec playwright test --config playwright.config.mts

- name: Collect diagnostics
if: failure()
run: |
DIAG_DIR="$RUNNER_TEMP/consumer-journey-diagnostics"
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
DIAG_DIR="$RUNNER_TEMP/consumer-journey-diagnostics-${{ matrix.framework }}"
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/myapp"
mkdir -p "$DIAG_DIR/generated-app"
if [ -d "$APP_DIR" ]; then
(
Expand All @@ -711,15 +730,15 @@ jobs:
> "$DIAG_DIR/logs.json" 2> "$DIAG_DIR/logs.stderr.log"
) || true
fi
cp "$RUNNER_TEMP/next-app.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/${{ matrix.framework }}-app.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/verdaccio.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/doctor.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/doctor.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/start.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/start.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/setup.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/setup.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/metadata.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/doctor.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/doctor.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/start.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/start.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/setup.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/setup.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/metadata.json" "$DIAG_DIR/" 2>/dev/null || true
cp "$APP_DIR/.zitadel/local/runtime.json" "$DIAG_DIR/runtime.json" 2>/dev/null || true
cp "$APP_DIR/package.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
cp "$APP_DIR/package-lock.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
Expand All @@ -728,19 +747,19 @@ jobs:
if: failure()
uses: actions/upload-artifact@v7
with:
name: consumer-journey-diagnostics
name: consumer-journey-diagnostics-${{ matrix.framework }}
path: |
apps/cli-journey-e2e/test-output/playwright/report/**
apps/cli-journey-e2e/test-output/playwright/output/**
${{ runner.temp }}/consumer-journey-diagnostics/**
apps/cli-journey-e2e/test-output/playwright/${{ matrix.framework }}/report/**
apps/cli-journey-e2e/test-output/playwright/${{ matrix.framework }}/output/**
${{ runner.temp }}/consumer-journey-diagnostics-${{ matrix.framework }}/**
if-no-files-found: ignore
retention-days: 7

- name: Tear down consumer journey services
if: always()
run: |
if [ -n "${NEXT_APP_PID:-}" ]; then kill "$NEXT_APP_PID" 2>/dev/null || true; fi
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
if [ -n "${JOURNEY_APP_PID:-}" ]; then kill "$JOURNEY_APP_PID" 2>/dev/null || true; fi
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey-${{ matrix.framework }}/myapp"
if [ -d "$APP_DIR" ]; then
(
cd "$APP_DIR"
Expand Down
16 changes: 9 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ Secrets").
- `apps/demo-next-e2e/` and `apps/demo-nuxt-e2e/` are the Playwright projects
that exercise each demo through real framework middleware against the
api-mock TCP server.
- `apps/cli-journey-e2e/` contains the fresh Next.js consumer journey
Playwright project. It installs local package tarballs through a temporary
registry and verifies CLI setup plus real registration/login flows.
- `apps/cli-journey-e2e/` contains the fresh consumer journey Playwright
project for every CLI-supported framework. It installs local package tarballs
through a temporary registry and verifies CLI setup plus real
registration/login flows.
- `packages/components/` contains shared Lit components.
- `packages/sdk-core/`, `packages/sdk-next/`, `packages/sdk-nuxt/`,
`packages/sdk-react/`, `packages/sdk-vue/`, and `packages/sdk-angular/`
Expand Down Expand Up @@ -148,10 +149,11 @@ corepack pnpm run journey
This runner requires Docker for Verdaccio and the CLI-managed local runtime. By
default it builds a local runtime image, ensures the Playwright Chromium
browsers are installed, builds and packs local npm packages with pnpm, creates
an empty app directory outside the repo, runs `npx @zitadel/cli@alpha doctor`,
`start`, and `setup --framework next --server local`, starts the generated app
on `localhost`, and runs Playwright with one worker. Use
`-- --image <docker-tag>` to reuse an existing local runtime image.
empty app directories outside the repo, runs `npx @zitadel/cli@alpha doctor`,
`start`, and `setup --framework <id> --server local`, starts the generated apps
on `localhost`, and runs Playwright with one worker per framework journey. Use
`-- --framework next` to run one framework and `-- --image <docker-tag>` to
reuse an existing local runtime image.

Use `corepack pnpm run journey` for deterministic CI-style proof of the
fresh-app path. Use `corepack pnpm run cli -- ...` for manual browser or agent
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ corepack pnpm run journey
This opt-in check ensures the Playwright Chromium browsers are installed, builds
the local npm packages, publishes them to a temporary Verdaccio registry, runs
`npx @zitadel/cli@alpha doctor`, `start`, and
`setup --framework next --server local` in an empty app directory, starts the
generated app, and verifies registration/login journeys.
`setup --framework <id> --server local` in fresh app directories for every
supported framework, starts the generated apps, and verifies registration/login
journeys. Use `corepack pnpm run journey -- --framework next` to run only the
Next.js journey.

Use `corepack pnpm run journey` for deterministic CI-style proof. Use
`corepack pnpm run cli -- ...` when you want to drive the same local package
Expand Down
12 changes: 6 additions & 6 deletions apps/cli-journey-e2e/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ These instructions apply to `apps/cli-journey-e2e/**`. Defer to the root

## Purpose

This project protects the customer local setup journey. Tests must exercise a
fresh app directory that runs the CLI local runtime path (`doctor`, `start`,
`setup --server local`) before starting the generated Next.js app. It must not
test the checked-in demo apps.
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.

## Maintenance Rules

Expand All @@ -20,8 +20,8 @@ test the checked-in demo apps.
- CI must install Zitadel packages from current workflow tarballs through the
temporary Verdaccio registry, not from public npm.
- CI must run `npx @zitadel/cli@alpha doctor`, `start`, and
`setup --framework next --server local` from the fresh app directory with
`--non-interactive --json`.
`setup --framework <next|nuxt|react|vue|angular> --server local` from the
fresh app directory with `--non-interactive --json`.
- Pack and upload only the public packages:
`@zitadel/cli`, `@zitadel/api`, `@zitadel/components`,
`@zitadel/sdk-core`, `@zitadel/sdk-next`, `@zitadel/sdk-nuxt`,
Expand Down
55 changes: 32 additions & 23 deletions apps/cli-journey-e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ Fresh-app Playwright coverage for the customer local setup journey.

This project is private test infrastructure. It does not test the checked-in
demo apps. Instead, it builds the current workspace packages, publishes packed
tarballs to a temporary registry, creates an empty app directory outside the
repo, runs the customer local CLI flow through `npx`, starts the generated app,
and verifies that a real user can register, log out, and log in again.
tarballs to a temporary registry, creates empty app directories outside the
repo, runs the customer local CLI flow through `npx`, starts the generated apps,
and verifies that real users can register, log out, and log in again across
Next, Nuxt, React, Vue, and Angular.

## Local runner

```sh
corepack pnpm run journey
```

The default mode uses Docker for Verdaccio and for the CLI-managed local
runtime. It builds a local runtime image unless `--image` is provided:
The default mode runs the full framework matrix in parallel. It uses Docker for
Verdaccio and for the CLI-managed local runtimes, and builds a local runtime
image unless `--image` is provided:

1. Ensure the Playwright Chromium browsers are installed.
2. Build the public workspace packages.
Expand All @@ -25,21 +27,26 @@ runtime. It builds a local runtime image unless `--image` is provided:
5. Start Verdaccio with npmjs proxying enabled.
6. Publish tarballs to Verdaccio with `alpha` and `latest` tags.
7. Build or use a local runtime Docker image for `zitadel start`.
8. Create an empty app directory in a temporary directory.
8. Create one empty app directory per selected framework in a temporary directory.
9. Run `npx <cli-package>@alpha doctor --non-interactive --json`.
10. Run `npx <cli-package>@alpha start --non-interactive --json`.
11. Run `npx <cli-package>@alpha setup --framework next --server local --non-interactive --json`.
12. Start the generated app on `localhost`.
13. Run the Playwright tests with one worker.
11. Run `npx <cli-package>@alpha setup --framework <id> --server local --non-interactive --json`.
12. Start each generated app on `localhost`.
13. Run the Playwright tests with one worker per framework journey.

### Options

```sh
corepack pnpm run journey -- --keep
corepack pnpm run journey -- --work-dir /tmp/zitadel-journey
corepack pnpm run journey -- --image nextgen:local
corepack pnpm run journey -- --framework next
corepack pnpm run journey -- --concurrency 2
```

- `--framework <id>` runs one framework (`next`, `nuxt`, `react`, `vue`, or
`angular`) instead of the full matrix.
- `--concurrency <n>` controls local framework parallelism. The default is `5`.
- `--image <docker-tag>` uses an existing local runtime image instead of
building one.
- `--keep` keeps the temporary work directory after success.
Expand All @@ -48,29 +55,33 @@ corepack pnpm run journey -- --image nextgen:local
Useful environment overrides:

- `JOURNEY_REGISTRY_PORT`
- `JOURNEY_APP_PORT`
- `JOURNEY_APP_PORT` for single-framework runs only
- `JOURNEY_ZITADEL_PORT` for single-framework runs only
- `JOURNEY_ENABLE_PASSKEY=0` as a local-only escape hatch while debugging
passkey setup. CI must run passkey coverage.

## CI gate

The `consumer-journey-e2e` workflow job does not use public Zitadel packages or
GHCR images. It downloads the GoReleaser snapshot image and the public npm
package tarballs produced by the same workflow, publishes those tarballs to
Verdaccio, points `ZITADEL_LOCAL_IMAGE` at the loaded image, runs the same
`npx` local setup flow, and runs the same Playwright project against the
generated app. Private support packages such as design tokens are bundled into
the public packages that need them and must not be uploaded or published.
The `consumer-journey-e2e` workflow job runs as a framework matrix. Each matrix
leg does not use public Zitadel packages or GHCR images. It downloads the
GoReleaser snapshot image and the public npm package tarballs produced by the
same workflow, publishes those tarballs to Verdaccio, points
`ZITADEL_LOCAL_IMAGE` at the loaded image, runs the same `npx` local setup flow,
and runs the same Playwright project against the generated app. Private support
packages such as design tokens are bundled into the public packages that need
them and must not be uploaded or published.

Failure diagnostics intentionally stay small: Playwright report/output,
doctor/start/setup JSON and stderr, local runtime metadata/logs, metadata,
generated app `package.json` and `package-lock.json`, Verdaccio logs, and Next
logs. Do not upload generated app `node_modules` or `.next` directories.
generated app `package.json` and `package-lock.json`, Verdaccio logs, and
generated app logs. Do not upload generated app `node_modules` or framework
build directories.

## Coverage

The suite is serial and one-worker because every test shares the same fresh
backend and generated app instance.
Each framework suite is serial and one-worker because every test in that suite
shares the same fresh backend and generated app instance. Local all-framework
runs execute framework suites in parallel.

- CLI local setup contract: doctor, start, and setup exit successfully, stdout
parses as JSON, `status` is `ok`, the generated app depends on the local SDK
Expand All @@ -79,5 +90,3 @@ backend and generated app instance.
out, and log in again with password.
- Passkey-only account: register with email/passkey, log out, and log in again
with passkey.
- Password-plus-passkey account: register with password, accept passkey setup,
log out, log in with password, log out, and log in with passkey.
2 changes: 1 addition & 1 deletion apps/cli-journey-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"test": "node --test scripts/*.test.mjs",
"prepare-app": "node scripts/prepare-next-app.mjs",
"prepare-app": "node scripts/prepare-app.mjs",
"publish-tarballs": "node scripts/publish-tarballs.mjs",
"verify-tarballs": "node scripts/verify-tarballs.mjs",
"e2e-local": "node scripts/run-local.mjs",
Expand Down
8 changes: 6 additions & 2 deletions apps/cli-journey-e2e/playwright.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { nxE2EPreset } from "@nx/playwright/preset";
import { defineConfig, devices } from "@playwright/test";

const baseURL = process.env.JOURNEY_APP_URL ?? "http://localhost:3000";
const outputDir =
process.env.JOURNEY_PLAYWRIGHT_OUTPUT_DIR ?? "./test-output/playwright/output";
const reportDir =
process.env.JOURNEY_PLAYWRIGHT_REPORT_DIR ?? "./test-output/playwright/report";

export default defineConfig({
...nxE2EPreset(import.meta.filename, { testDir: "./src" }),
fullyParallel: false,
workers: 1,
reporter: [["html", { outputFolder: "./test-output/playwright/report", open: "never" }]],
reporter: [["html", { outputFolder: reportDir, open: "never" }]],
use: {
baseURL,
trace: "retain-on-failure",
video: "retain-on-failure",
},
outputDir: "./test-output/playwright/output",
outputDir,
projects: [
{
name: "chromium",
Expand Down
Loading
Loading