Skip to content

Commit 08b7ab4

Browse files
authored
fix: allow local setup to scaffold fresh app directories (#272)
## Summary - Let `setup --server local` scaffold into a fresh directory without requiring a prebuilt Next.js app - Preserve runtime-only `.zitadel/local` state when bootstrapping in place - Normalize local command `--cwd` handling and update docs and tests for the new flow ## Testing - Added unit coverage for fresh-directory scaffolding, local runtime preservation, and relative `--cwd` handling - Not run (not requested)
1 parent 0cc8cc3 commit 08b7ab4

36 files changed

Lines changed: 1106 additions & 456 deletions
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Exercise the customer local setup journey in the consumer e2e gate.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@zitadel/cli": patch
3+
---
4+
5+
Allow same-directory setup after starting the local Zitadel runtime.

.changeset/pr-metadata-guidance.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Document PR title and description expectations for agents.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Summary
2+
3+
<!-- Briefly describe what changed and why. -->
4+
5+
-
6+
7+
## Validation
8+
9+
<!-- List exact commands run. If validation was not run, say so explicitly. -->
10+
11+
-
12+
13+
## Release notes / changeset
14+
15+
<!-- Mention the changeset status for user-visible package changes. -->
16+
17+
-
18+
19+
## Notes
20+
21+
<!-- Add reviewer context, follow-ups, risks, or "None". -->
22+
23+
-

.github/copilot-instructions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ more than polish.
2727
`.changeset/<slug>.md` file directly rather than via the interactive prompt.
2828
See `AGENTS.md` for the package list and file format. npm package manifests
2929
must keep `"license": "MIT"`.
30-
- PR titles must pass Semantic PR. Use `<type>(optional-scope): <summary>` and
31-
verify allowed types/scopes against `.github/semantic.yml`; for docs-only
32-
changes, use a title such as `docs: add preview status disclaimer`.
30+
- PR metadata must follow `AGENTS.md`: verify the title against
31+
`.github/semantic.yml`, prefer a scope-free title when unsure, and keep the
32+
PR description current with summary, validation, changeset, and notes.
3333
- Server and embedded console changes are AGPL-3.0-only by default; public API,
3434
docs, CLI, and SDK paths are MIT exceptions per `LICENSING.md`.
3535
- For `consumer-journey-e2e` or `apps/cli-journey-e2e/**` changes, verify that

.github/instructions/cli.instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@ The CLI is an agent-facing product surface. Review changes against
2626
- Changes to command names, flags, envelope shape, server resolution, renderer
2727
direction, or agent guidance should update tests, `apps/cli/SKILLS.md`, and
2828
the generated README command section.
29+
- PR descriptions for CLI changes should call out user-visible command behavior,
30+
updated agent/docs surfaces, and the focused validation commands that were
31+
actually run.
2932
- Mock behavior should be explicit and must not be presented as live platform
3033
behavior.

.github/instructions/consumer-journey.instructions.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ applyTo: "apps/cli-journey-e2e/**,.github/workflows/ci.yml"
44

55
# Consumer Journey Review Instructions
66

7-
Review consumer journey changes as a required fresh-app quality gate, not as a
8-
demo-app e2e suite.
7+
Review consumer journey changes as the required customer local setup quality
8+
gate, not as a demo-app e2e suite.
99

1010
- CI must consume the current workflow's GoReleaser image and npm package
1111
tarballs. Do not replace this with public npm packages for Zitadel packages.
12+
- The journey must exercise the customer local runtime flow through `npx`:
13+
`doctor`, `start`, then `setup --framework next --server local` with
14+
`--non-interactive --json`.
1215
- Produce package artifacts with `corepack pnpm --dir <package> pack` and keep
1316
tarball verification for required package presence plus unresolved
1417
`catalog:` or `workspace:` dependency specs.
@@ -23,6 +26,7 @@ demo-app e2e suite.
2326
- Browser tests should run serially with one worker, use `localhost` for
2427
WebAuthn, and require passkey coverage in CI. `JOURNEY_ENABLE_PASSKEY=0` is
2528
only a local debugging escape hatch.
26-
- Failure artifacts should include Playwright output/report, setup JSON,
27-
setup stderr, metadata, generated app package manifests, Verdaccio logs, Next
28-
logs, and backend logs. Do not upload generated `node_modules` or `.next`.
29+
- Failure artifacts should include Playwright output/report, doctor/start/setup
30+
JSON and stderr, local runtime metadata/logs, metadata, generated app package
31+
manifests, Verdaccio logs, and Next logs. Do not upload generated
32+
`node_modules` or `.next`.

.github/instructions/typescript.instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Vitest.
1919
- User-visible changes to those packages need a changeset; write the
2020
`.changeset/<slug>.md` file directly rather than via the interactive prompt.
2121
See `AGENTS.md` for the package list and file format.
22+
- PR descriptions for public package changes should mention the changeset
23+
status and list the focused package validation commands that were actually
24+
run.
2225
- Avoid committing generated `dist/**` churn unless the release or package smoke
2326
check explicitly requires it.
2427
- Respect peer dependencies in `packages/sdk-next`; do not bundle React, Next,

.github/workflows/ci.yml

Lines changed: 34 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -305,75 +305,6 @@ jobs:
305305
docker load -i goreleaser-dist/nextgen-image.tar
306306
docker image inspect -f '{{.Os}}/{{.Architecture}}' "$(cat goreleaser-dist/nextgen-image.tag)" | grep -q '^linux/amd64$'
307307
308-
- name: Smoke test CLI-style local runtime image
309-
run: |
310-
set -euo pipefail
311-
IMAGE="$(cat goreleaser-dist/nextgen-image.tag)"
312-
DATA_DIR="$RUNNER_TEMP/nextgen-dev-data"
313-
RUNTIME_DIR="$RUNNER_TEMP/nextgen-dev-runtime"
314-
HOST_UID="$(id -u)"
315-
HOST_GID="$(id -g)"
316-
PASSWD_FILE="$RUNTIME_DIR/container-passwd"
317-
GROUP_FILE="$RUNTIME_DIR/container-group"
318-
mkdir -p "$DATA_DIR" "$RUNTIME_DIR"
319-
# Mirror `zitadel start`: run the container as the host user so
320-
# bind-mounted state stays editable, and provide passwd/group entries
321-
# because embedded Postgres initdb refuses unknown numeric users.
322-
{
323-
echo "root:x:0:0:root:/root:/bin/sh"
324-
echo "nonroot:x:65532:65532:nonroot:/nonexistent:/usr/sbin/nologin"
325-
echo "zitadel-local:x:${HOST_UID}:${HOST_GID}:Zitadel local user:/tmp:/usr/sbin/nologin"
326-
} > "$PASSWD_FILE"
327-
{
328-
echo "root:x:0:"
329-
echo "nonroot:x:65532:"
330-
echo "zitadel-local:x:${HOST_GID}:"
331-
} > "$GROUP_FILE"
332-
CONTAINER="$(
333-
docker run -d \
334-
-p 127.0.0.1:18080:8080 \
335-
-v "$DATA_DIR:/var/lib/zitadel/nextgen-data" \
336-
-v "$PASSWD_FILE:/etc/passwd:ro" \
337-
-v "$GROUP_FILE:/etc/group:ro" \
338-
--user "${HOST_UID}:${HOST_GID}" \
339-
-e NEXTGEN_SERVER_ADDRESS=:8080 \
340-
-e NEXTGEN_SERVER_DATA_DIR=/var/lib/zitadel/nextgen-data \
341-
"$IMAGE"
342-
)"
343-
cleanup() {
344-
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
345-
}
346-
diagnose() {
347-
docker logs "$CONTAINER" || true
348-
}
349-
trap cleanup EXIT
350-
351-
ready=0
352-
for _ in $(seq 1 120); do
353-
if curl -fsS http://localhost:18080/healthz >/dev/null; then
354-
ready=1
355-
break
356-
fi
357-
sleep 1
358-
done
359-
if [ "$ready" -ne 1 ]; then
360-
echo "::error::timed out waiting for zero-config /healthz"
361-
diagnose
362-
exit 1
363-
fi
364-
test -f "$DATA_DIR/server-encryption-key"
365-
if ! curl -fsS -o /dev/null -w "%{http_code}\n" http://localhost:18080/ui/console/ | grep -q '^200$'; then
366-
echo "::error::zero-config /ui/console/ did not return 200"
367-
diagnose
368-
exit 1
369-
fi
370-
if ! curl -fsS -o /dev/null -w "%{http_code}\n" http://localhost:18080/ui/login/ | grep -q '^200$'; then
371-
echo "::error::zero-config /ui/login/ did not return 200"
372-
diagnose
373-
exit 1
374-
fi
375-
echo "CLI-style local runtime image smoke test passed"
376-
377308
- name: Prepare quick-start compose stack
378309
run: |
379310
mkdir -p nextgen_quickstart
@@ -400,8 +331,9 @@ jobs:
400331
- name: Start stack and verify HTTP endpoints
401332
run: |
402333
set -euo pipefail
403-
# Separate from the CLI-style smoke above: this keeps the documented
404-
# compose fallback valid for operators/manual quick-start usage.
334+
# Separate from the customer local setup journey: this keeps the
335+
# documented compose fallback valid for operators/manual quick-start
336+
# usage.
405337
COMPOSE=(docker compose -f nextgen_quickstart/docker-compose.yaml --env-file nextgen_quickstart/.env)
406338
"${COMPOSE[@]}" up -d --wait
407339
ready=0
@@ -682,32 +614,11 @@ jobs:
682614
test -f goreleaser-dist/nextgen-image.tag
683615
docker load -i goreleaser-dist/nextgen-image.tar
684616
docker image inspect -f '{{.Os}}/{{.Architecture}}' "$(cat goreleaser-dist/nextgen-image.tag)" | grep -q '^linux/amd64$'
685-
686-
- name: Start backend stack
687-
run: |
688-
mkdir -p "$RUNNER_TEMP/nextgen-journey"
689-
cp docs/operations/docker-compose.yaml "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml"
690-
IMAGE="$(cat goreleaser-dist/nextgen-image.tag)"
691-
{
692-
echo "NEXTGEN_IMAGE=${IMAGE}"
693-
echo "NEXTGEN_PORT=8080"
694-
} > "$RUNNER_TEMP/nextgen-journey/.env"
695-
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" up -d --wait
696-
for _ in $(seq 1 90); do
697-
if curl -fsS http://localhost:8080/healthz >/dev/null; then
698-
exit 0
699-
fi
700-
sleep 1
701-
done
702-
echo "::error::timed out waiting for backend /healthz"
703-
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" logs nextgen
704-
exit 1
617+
echo "ZITADEL_LOCAL_IMAGE=$(cat goreleaser-dist/nextgen-image.tag)" >> "$GITHUB_ENV"
705618
706619
- name: Prepare fresh Next.js app
707620
id: prepare-app
708621
env:
709-
JOURNEY_BACKEND_URL: http://localhost:8080
710-
JOURNEY_CREATE_NEXT_APP_VERSION: 16.2.4
711622
JOURNEY_REGISTRY_URL: http://127.0.0.1:4873
712623
JOURNEY_WORK_DIR: ${{ runner.temp }}/zitadel-cli-journey
713624
run: node apps/cli-journey-e2e/scripts/prepare-next-app.mjs
@@ -735,17 +646,32 @@ jobs:
735646
- name: Collect diagnostics
736647
if: failure()
737648
run: |
738-
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
739649
DIAG_DIR="$RUNNER_TEMP/consumer-journey-diagnostics"
650+
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
740651
mkdir -p "$DIAG_DIR/generated-app"
741-
cp "$RUNNER_TEMP/backend-compose.log" "$DIAG_DIR/" 2>/dev/null || true
652+
if [ -d "$APP_DIR" ]; then
653+
(
654+
cd "$APP_DIR"
655+
npm_config_registry=http://127.0.0.1:4873 \
656+
npm_config_yes=true \
657+
npm_config_audit=false \
658+
npm_config_fund=false \
659+
npx --yes @zitadel/cli@alpha logs --tail 400 --non-interactive --json \
660+
> "$DIAG_DIR/logs.json" 2> "$DIAG_DIR/logs.stderr.log"
661+
) || true
662+
fi
742663
cp "$RUNNER_TEMP/next-app.log" "$DIAG_DIR/" 2>/dev/null || true
743664
cp "$RUNNER_TEMP/verdaccio.log" "$DIAG_DIR/" 2>/dev/null || true
665+
cp "$RUNNER_TEMP/zitadel-cli-journey/doctor.json" "$DIAG_DIR/" 2>/dev/null || true
666+
cp "$RUNNER_TEMP/zitadel-cli-journey/doctor.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
667+
cp "$RUNNER_TEMP/zitadel-cli-journey/start.json" "$DIAG_DIR/" 2>/dev/null || true
668+
cp "$RUNNER_TEMP/zitadel-cli-journey/start.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
744669
cp "$RUNNER_TEMP/zitadel-cli-journey/setup.json" "$DIAG_DIR/" 2>/dev/null || true
745670
cp "$RUNNER_TEMP/zitadel-cli-journey/setup.stderr.log" "$DIAG_DIR/" 2>/dev/null || true
746671
cp "$RUNNER_TEMP/zitadel-cli-journey/metadata.json" "$DIAG_DIR/" 2>/dev/null || true
747-
cp "$RUNNER_TEMP/zitadel-cli-journey/myapp/package.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
748-
cp "$RUNNER_TEMP/zitadel-cli-journey/myapp/package-lock.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
672+
cp "$APP_DIR/.zitadel/local/runtime.json" "$DIAG_DIR/runtime.json" 2>/dev/null || true
673+
cp "$APP_DIR/package.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
674+
cp "$APP_DIR/package-lock.json" "$DIAG_DIR/generated-app/" 2>/dev/null || true
749675
750676
- name: Upload consumer journey diagnostics
751677
if: failure()
@@ -763,7 +689,15 @@ jobs:
763689
if: always()
764690
run: |
765691
if [ -n "${NEXT_APP_PID:-}" ]; then kill "$NEXT_APP_PID" 2>/dev/null || true; fi
766-
if [ -n "${VERDACCIO_PID:-}" ]; then kill "$VERDACCIO_PID" 2>/dev/null || true; fi
767-
if [ -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" ]; then
768-
docker compose -f "$RUNNER_TEMP/nextgen-journey/docker-compose.yaml" --env-file "$RUNNER_TEMP/nextgen-journey/.env" down -v
692+
APP_DIR="$RUNNER_TEMP/zitadel-cli-journey/myapp"
693+
if [ -d "$APP_DIR" ]; then
694+
(
695+
cd "$APP_DIR"
696+
npm_config_registry=http://127.0.0.1:4873 \
697+
npm_config_yes=true \
698+
npm_config_audit=false \
699+
npm_config_fund=false \
700+
npx --yes @zitadel/cli@alpha reset --force --non-interactive --json
701+
) || true
769702
fi
703+
if [ -n "${VERDACCIO_PID:-}" ]; then kill "$VERDACCIO_PID" 2>/dev/null || true; fi

AGENTS.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@ The local reproduction command for the fresh-app consumer journey gate is:
137137
corepack pnpm run journey
138138
```
139139

140-
This runner requires Docker for Verdaccio. By default it starts the backend from
141-
source with embedded Postgres, ensures the Playwright Chromium browsers are
142-
installed, builds and packs local npm packages with pnpm, creates a temporary
143-
Next.js app outside the repo, runs CLI setup through npm, starts the generated
144-
app on `localhost`, and runs Playwright with one worker. Use
145-
`-- --backend image --image <docker-tag>` to run the backend through the local
146-
compose profile for image parity.
140+
This runner requires Docker for Verdaccio and the CLI-managed local runtime. By
141+
default it builds a local runtime image, ensures the Playwright Chromium
142+
browsers are installed, builds and packs local npm packages with pnpm, creates
143+
an empty app directory outside the repo, runs `npx @zitadel/cli@alpha doctor`,
144+
`start`, and `setup --framework next --server local`, starts the generated app
145+
on `localhost`, and runs Playwright with one worker. Use
146+
`-- --image <docker-tag>` to reuse an existing local runtime image.
147147

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

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

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

209209
## Release, Licensing, And Secrets
210210

211-
- PR titles must pass the Semantic PR check. Use the conventional format
212-
`<type>(optional-scope): <summary>` and treat `.github/semantic.yml` as the
211+
- PR titles must pass the Semantic PR check. Before publishing, opening, or
212+
renaming a PR, inspect `.github/semantic.yml` and use the conventional format
213+
`<type>(optional-scope): <summary>`. Treat `.github/semantic.yml` as the
213214
source of truth for allowed types and scopes. Scopes are optional; omit the
214-
scope instead of inventing one. For documentation-only changes, use the
215-
`docs` type, for example `docs: add preview status disclaimer`.
215+
scope when unsure or when no allowed scope fits. Do not invent scopes. For
216+
documentation-only changes, use the `docs` type, for example
217+
`docs: add preview status disclaimer`.
218+
- Agent-created or agent-updated PRs must include a concise description before
219+
handoff. Use sections for `Summary`, `Validation`,
220+
`Release notes / changeset`, and `Notes`. List the exact validation commands
221+
run; if validation was not run, say so explicitly. Mention changeset status
222+
for user-visible package changes.
216223
- User-visible changes to a public npm package need a changeset. The public
217224
packages are `@zitadel/cli` (`apps/cli/`), `@zitadel/api`,
218225
`@zitadel/components`, `@zitadel/sdk-core`, `@zitadel/sdk-next`,

0 commit comments

Comments
 (0)