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
4 changes: 4 additions & 0 deletions .changeset/customer-local-journey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Exercise the customer local setup journey in the consumer e2e gate.
5 changes: 5 additions & 0 deletions .changeset/local-runtime-fresh-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zitadel/cli": patch
---

Allow same-directory setup after starting the local Zitadel runtime.
4 changes: 4 additions & 0 deletions .changeset/pr-metadata-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Document PR title and description expectations for agents.
23 changes: 23 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Summary

<!-- Briefly describe what changed and why. -->

-

## Validation

<!-- List exact commands run. If validation was not run, say so explicitly. -->

-

## Release notes / changeset

<!-- Mention the changeset status for user-visible package changes. -->

-

## Notes

<!-- Add reviewer context, follow-ups, risks, or "None". -->

-
6 changes: 3 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ more than polish.
`.changeset/<slug>.md` file directly rather than via the interactive prompt.
See `AGENTS.md` for the package list and file format. npm package manifests
must keep `"license": "MIT"`.
- PR titles must pass Semantic PR. Use `<type>(optional-scope): <summary>` and
verify allowed types/scopes against `.github/semantic.yml`; for docs-only
changes, use a title such as `docs: add preview status disclaimer`.
- PR metadata must follow `AGENTS.md`: verify the title against
`.github/semantic.yml`, prefer a scope-free title when unsure, and keep the
PR description current with summary, validation, changeset, and notes.
- Server and embedded console changes are AGPL-3.0-only by default; public API,
docs, CLI, and SDK paths are MIT exceptions per `LICENSING.md`.
- For `consumer-journey-e2e` or `apps/cli-journey-e2e/**` changes, verify that
Expand Down
3 changes: 3 additions & 0 deletions .github/instructions/cli.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ The CLI is an agent-facing product surface. Review changes against
- Changes to command names, flags, envelope shape, server resolution, renderer
direction, or agent guidance should update tests, `apps/cli/SKILLS.md`, and
the generated README command section.
- PR descriptions for CLI changes should call out user-visible command behavior,
updated agent/docs surfaces, and the focused validation commands that were
actually run.
- Mock behavior should be explicit and must not be presented as live platform
behavior.
14 changes: 9 additions & 5 deletions .github/instructions/consumer-journey.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ applyTo: "apps/cli-journey-e2e/**,.github/workflows/ci.yml"

# Consumer Journey Review Instructions

Review consumer journey changes as a required fresh-app quality gate, not as a
demo-app e2e suite.
Review consumer journey changes as the required customer local setup quality
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
`--non-interactive --json`.
- Produce package artifacts with `corepack pnpm --dir <package> pack` and keep
tarball verification for required package presence plus unresolved
`catalog:` or `workspace:` dependency specs.
Expand All @@ -23,6 +26,7 @@ demo-app e2e suite.
- 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, setup JSON,
setup stderr, metadata, generated app package manifests, Verdaccio logs, Next
logs, and backend logs. Do not upload generated `node_modules` or `.next`.
- 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
`node_modules` or `.next`.
3 changes: 3 additions & 0 deletions .github/instructions/typescript.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Vitest.
- User-visible changes to those packages need a changeset; write the
`.changeset/<slug>.md` file directly rather than via the interactive prompt.
See `AGENTS.md` for the package list and file format.
- PR descriptions for public package changes should mention the changeset
status and list the focused package validation commands that were actually
run.
- Avoid committing generated `dist/**` churn unless the release or package smoke
check explicitly requires it.
- Respect peer dependencies in `packages/sdk-next`; do not bundle React, Next,
Expand Down
134 changes: 34 additions & 100 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,75 +305,6 @@ jobs:
docker load -i goreleaser-dist/nextgen-image.tar
docker image inspect -f '{{.Os}}/{{.Architecture}}' "$(cat goreleaser-dist/nextgen-image.tag)" | grep -q '^linux/amd64$'

- name: Smoke test CLI-style local runtime image
run: |
set -euo pipefail
IMAGE="$(cat goreleaser-dist/nextgen-image.tag)"
DATA_DIR="$RUNNER_TEMP/nextgen-dev-data"
RUNTIME_DIR="$RUNNER_TEMP/nextgen-dev-runtime"
HOST_UID="$(id -u)"
HOST_GID="$(id -g)"
PASSWD_FILE="$RUNTIME_DIR/container-passwd"
GROUP_FILE="$RUNTIME_DIR/container-group"
mkdir -p "$DATA_DIR" "$RUNTIME_DIR"
# Mirror `zitadel start`: run the container as the host user so
# bind-mounted state stays editable, and provide passwd/group entries
# because embedded Postgres initdb refuses unknown numeric users.
{
echo "root:x:0:0:root:/root:/bin/sh"
echo "nonroot:x:65532:65532:nonroot:/nonexistent:/usr/sbin/nologin"
echo "zitadel-local:x:${HOST_UID}:${HOST_GID}:Zitadel local user:/tmp:/usr/sbin/nologin"
} > "$PASSWD_FILE"
{
echo "root:x:0:"
echo "nonroot:x:65532:"
echo "zitadel-local:x:${HOST_GID}:"
} > "$GROUP_FILE"
CONTAINER="$(
docker run -d \
-p 127.0.0.1:18080:8080 \
-v "$DATA_DIR:/var/lib/zitadel/nextgen-data" \
-v "$PASSWD_FILE:/etc/passwd:ro" \
-v "$GROUP_FILE:/etc/group:ro" \
--user "${HOST_UID}:${HOST_GID}" \
-e NEXTGEN_SERVER_ADDRESS=:8080 \
-e NEXTGEN_SERVER_DATA_DIR=/var/lib/zitadel/nextgen-data \
"$IMAGE"
)"
cleanup() {
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
}
diagnose() {
docker logs "$CONTAINER" || true
}
trap cleanup EXIT

ready=0
for _ in $(seq 1 120); do
if curl -fsS http://localhost:18080/healthz >/dev/null; then
ready=1
break
fi
sleep 1
done
if [ "$ready" -ne 1 ]; then
echo "::error::timed out waiting for zero-config /healthz"
diagnose
exit 1
fi
test -f "$DATA_DIR/server-encryption-key"
if ! curl -fsS -o /dev/null -w "%{http_code}\n" http://localhost:18080/ui/console/ | grep -q '^200$'; then
echo "::error::zero-config /ui/console/ did not return 200"
diagnose
exit 1
fi
if ! curl -fsS -o /dev/null -w "%{http_code}\n" http://localhost:18080/ui/login/ | grep -q '^200$'; then
echo "::error::zero-config /ui/login/ did not return 200"
diagnose
exit 1
fi
echo "CLI-style local runtime image smoke test passed"

- name: Prepare quick-start compose stack
run: |
mkdir -p nextgen_quickstart
Expand All @@ -400,8 +331,9 @@ jobs:
- name: Start stack and verify HTTP endpoints
run: |
set -euo pipefail
# Separate from the CLI-style smoke above: this keeps the documented
# compose fallback valid for operators/manual quick-start usage.
# Separate from the customer local setup journey: this keeps the
# documented compose fallback valid for operators/manual quick-start
# usage.
COMPOSE=(docker compose -f nextgen_quickstart/docker-compose.yaml --env-file nextgen_quickstart/.env)
"${COMPOSE[@]}" up -d --wait
ready=0
Expand Down Expand Up @@ -682,32 +614,11 @@ jobs:
test -f goreleaser-dist/nextgen-image.tag
docker load -i goreleaser-dist/nextgen-image.tar
docker image inspect -f '{{.Os}}/{{.Architecture}}' "$(cat goreleaser-dist/nextgen-image.tag)" | grep -q '^linux/amd64$'

- name: Start backend stack
run: |
mkdir -p "$RUNNER_TEMP/nextgen-journey"
cp docs/operations/docker-compose.yaml "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml"
IMAGE="$(cat goreleaser-dist/nextgen-image.tag)"
{
echo "NEXTGEN_IMAGE=${IMAGE}"
echo "NEXTGEN_PORT=8080"
} > "$RUNNER_TEMP/nextgen-journey/.env"
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" up -d --wait
for _ in $(seq 1 90); do
if curl -fsS http://localhost:8080/healthz >/dev/null; then
exit 0
fi
sleep 1
done
echo "::error::timed out waiting for backend /healthz"
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" logs nextgen
exit 1
echo "ZITADEL_LOCAL_IMAGE=$(cat goreleaser-dist/nextgen-image.tag)" >> "$GITHUB_ENV"

- name: Prepare fresh Next.js app
id: prepare-app
env:
JOURNEY_BACKEND_URL: http://localhost:8080
JOURNEY_CREATE_NEXT_APP_VERSION: 16.2.4
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
Expand Down Expand Up @@ -735,17 +646,32 @@ jobs:
- name: Collect diagnostics
if: failure()
run: |
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" logs > "$RUNNER_TEMP/backend-compose.log" 2>&1 || true
DIAG_DIR="$RUNNER_TEMP/consumer-journey-diagnostics"
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
mkdir -p "$DIAG_DIR/generated-app"
cp "$RUNNER_TEMP/backend-compose.log" "$DIAG_DIR/" 2>/dev/null || true
if [ -d "$APP_DIR" ]; then
(
cd "$APP_DIR"
npm_config_registry=http://127.0.0.1:4873 \
npm_config_yes=true \
npm_config_audit=false \
npm_config_fund=false \
npx --yes @zitadel/cli@alpha logs --tail 400 --non-interactive --json \
> "$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/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/myapp/package.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
cp "$RUNNER_TEMP/zitadel-cli-journey/myapp/package-lock.json" "$DIAG_DIR/generated-app/" 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

- name: Upload consumer journey diagnostics
if: failure()
Expand All @@ -763,7 +689,15 @@ jobs:
if: always()
run: |
if [ -n "${NEXT_APP_PID:-}" ]; then kill "$NEXT_APP_PID" 2>/dev/null || true; fi
if [ -n "${VERDACCIO_PID:-}" ]; then kill "$VERDACCIO_PID" 2>/dev/null || true; fi
if [ -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" ]; then
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" down -v
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
if [ -d "$APP_DIR" ]; then
(
cd "$APP_DIR"
npm_config_registry=http://127.0.0.1:4873 \
npm_config_yes=true \
npm_config_audit=false \
npm_config_fund=false \
npx --yes @zitadel/cli@alpha reset --force --non-interactive --json
) || true
fi
if [ -n "${VERDACCIO_PID:-}" ]; then kill "$VERDACCIO_PID" 2>/dev/null || true; fi
31 changes: 19 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,13 @@ The local reproduction command for the fresh-app consumer journey gate is:
corepack pnpm run journey
```

This runner requires Docker for Verdaccio. By default it starts the backend from
source with embedded Postgres, ensures the Playwright Chromium browsers are
installed, builds and packs local npm packages with pnpm, creates a temporary
Next.js app outside the repo, runs CLI setup through npm, starts the generated
app on `localhost`, and runs Playwright with one worker. Use
`-- --backend image --image <docker-tag>` to run the backend through the local
compose profile for image parity.
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.

In CI the dedicated `node-e2e` job (in `.github/workflows/ci.yml`) gates merges
on the checked-in demo integrations. The separate `consumer-journey-e2e` job is
Expand All @@ -170,7 +170,7 @@ upward**. When deciding where a new test belongs:

The consumer journey suite is the exception to the checked-in demo ownership
rule: it belongs in `apps/cli-journey-e2e/` and must exercise a freshly
generated app because it protects the real CLI onboarding path.
generated app because it protects the customer local setup path.

A new test belongs at e2e level only when the boundary it covers is
exclusively the framework integration (middleware, cookie origin, full
Expand Down Expand Up @@ -208,11 +208,18 @@ For customer-local runtime workflows, agents should prefer

## Release, Licensing, And Secrets

- PR titles must pass the Semantic PR check. Use the conventional format
`<type>(optional-scope): <summary>` and treat `.github/semantic.yml` as the
- PR titles must pass the Semantic PR check. Before publishing, opening, or
renaming a PR, inspect `.github/semantic.yml` and use the conventional format
`<type>(optional-scope): <summary>`. Treat `.github/semantic.yml` as the
source of truth for allowed types and scopes. Scopes are optional; omit the
scope instead of inventing one. For documentation-only changes, use the
`docs` type, for example `docs: add preview status disclaimer`.
scope when unsure or when no allowed scope fits. Do not invent scopes. For
documentation-only changes, use the `docs` type, for example
`docs: add preview status disclaimer`.
- Agent-created or agent-updated PRs must include a concise description before
handoff. Use sections for `Summary`, `Validation`,
`Release notes / changeset`, and `Notes`. List the exact validation commands
run; if validation was not run, say so explicitly. Mention changeset status
for user-visible package changes.
- User-visible changes to a public npm package need a changeset. The public
packages are `@zitadel/cli` (`apps/cli/`), `@zitadel/api`,
`@zitadel/components`, `@zitadel/sdk-core`, `@zitadel/sdk-next`,
Expand Down
14 changes: 13 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
| --------------------------------- | -------------------------------------------------------------- |
| Check local runtime prerequisites | `npx @zitadel/cli@alpha doctor` |
| Start local Zitadel | `npx @zitadel/cli@alpha start` |
| Add auth to Next.js | `npx @zitadel/cli@alpha setup --framework next --server local` |
| Add auth to Next.js | `npx @zitadel/cli@alpha setup --server local` |
| Stop local Zitadel, keeping data | `npx @zitadel/cli@alpha stop` |
| Delete local Zitadel data | `npx @zitadel/cli@alpha reset --force` |

Expand Down Expand Up @@ -182,3 +182,15 @@ documentation-only changes, use the `docs` type, for example:
```text
docs: add preview status disclaimer
```

## Pull request descriptions

Include a concise PR description before handing work off for review. Use these
sections:

- `Summary` — what changed and why.
- `Validation` — exact commands run. If validation was not run, say so
explicitly.
- `Release notes / changeset` — changeset status for user-visible package
changes.
- `Notes` — reviewer context, follow-ups, risks, or `None`.
Loading
Loading