diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91c78a4d..8be38dee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,10 @@ on: pull_request jobs: quality: + env: + ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.63.4 + POSTGRES_IMAGE: postgres:17.0-alpine3.19 + name: Ensure Quality runs-on: ubuntu-latest @@ -21,6 +25,7 @@ jobs: - lint - test:unit - test:integration + - test:acceptance steps: - name: Checkout Repo @@ -34,12 +39,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4.0.0 - - uses: pnpm/action-setup@v4.0.0 - name: Install pnpm - id: pnpm-install - with: - run_install: false - - name: Get pnpm store directory id: pnpm-cache shell: bash @@ -54,17 +53,52 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- + - name: Install Dependencies + run: CYPRESS_INSTALL_BINARY=0 pnpm install + + - run: echo "CYPRESS_VERSION=$(pnpm list -r | grep cypress | cut -d ' ' -f 2)" >> $GITHUB_ENV + if: ${{ matrix.command == 'test:integration' }} + - uses: actions/cache@v4.0.2 name: Setup Cypress binary cache with: path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-binary-${{ hashFiles('**/pnpm-lock.yaml') }} + key: ${{ runner.os }}-cypress-binary-${{ env.CYPRESS_VERSION }} restore-keys: | ${{ runner.os }}-cypress-binary- - if: ${{ matrix.command }} == "test:integration" + if: ${{ matrix.command == 'test:integration' }} - - name: Install Dependencies + - name: Install Cypress Browsers run: pnpm install + if: ${{ matrix.command == 'test:integration' }} + + - run: echo "PLAYWRIGHT_VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_ENV + if: ${{ matrix.command == 'test:acceptance' }} + + - uses: actions/cache@v4.0.2 + name: Setup Playwright binary cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-binary-${{ env.PLAYWRIGHT_VERSION }} + restore-keys: | + ${{ runner.os }}-playwright-binary- + if: ${{ matrix.command == 'test:acceptance' }} + + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + if: ${{ matrix.command == 'test:acceptance' }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + if: ${{ matrix.command == 'test:acceptance' }} + + - name: Run ZITADEL + run: ZITADEL_DEV_UID=root pnpm run-zitadel + if: ${{ matrix.command == 'test:acceptance' }} + + - name: Install Playwright Browsers + run: pnpm build + if: ${{ matrix.command == 'test:acceptance' }} - name: Check id: check diff --git a/.gitignore b/.gitignore index 978119f3..5cf3100a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ packages/zitadel-server/src/app/proto .idea .vercel .env*.local +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/README.md b/README.md index ec33bf51..f1f8a930 100644 --- a/README.md +++ b/README.md @@ -178,32 +178,57 @@ To run the application make sure to install the dependencies with pnpm install ``` -then setup the environment for the login application which needs a `.env.local` in `/apps/login`. -Go to your instance and create a service user for the application having the `IAM_OWNER` manager role. -This user is required to have access to create users on your primary organization and reading policy data so it can be -restricted to your personal use case but we'll stick with `IAM_OWNER` for convenience. Create a PAT and copy the value to -paste it under the `ZITADEL_SERVICE_USER_TOKEN` key. -The file should look as follows: +then generate the GRPC stubs with -``` -ZITADEL_API_URL=[yourinstanceurl] -ZITADEL_ORG_ID=[yourprimaryorg] -ZITADEL_SERVICE_USER_TOKEN=[yourserviceuserpersonalaccesstoken] +```sh +pnpm generate ``` -then generate the GRPC stubs with +To run the application against a local ZITADEL instance, run the following command: ```sh -pnpm generate +pnpm run-zitadel +``` + +This sets up ZITADEL using docker compose and writes the configuration to the file `apps/login/.env.local`. + +
+Alternatively, use another environment +You can develop against any ZITADEL instance in which you have sufficient rights to execute the following steps. +Just create or overwrite the file `apps/login/.env.local` yourself. +Add your instances base URL to the file at the key `ZITADEL_API_URL`. +Go to your instance and create a service user for the login application. +The login application creates users on your primary organization and reads policy data. +For the sake of simplicity, just make the service user an instance member with the role `IAM_OWNER`. +Create a PAT and copy it to the file `apps/login/.env.local` using the key `ZITADEL_SERVICE_USER_TOKEN`. +Also add the users ID to the file using the key `ZITADEL_SERVICE_USER_ID`. + +The file should look similar to this: + +``` +ZITADEL_API_URL=https://zitadel-tlx3du.us1.zitadel.cloud +ZITADEL_SERVICE_USER_ID=289106423158521850 +ZITADEL_SERVICE_USER_TOKEN=1S6w48thfWFI2klgfwkCnhXJLf9FQ457E-_3H74ePQxfO3Af0Tm4V5Xi-ji7urIl_xbn-Rk ``` -and then run it with +
+ +Start the login application in dev mode: ```sh pnpm dev ``` Open the login application with your favorite browser at `localhost:3000`. +Change the source code and see the changes live in your browser. + +Make sure the application still behaves as expected by running all tests + +```sh +pnpm test +``` + +To satisfy your unique workflow requirements, check out the package.json in the root directory for more detailed scripts. ### Deploy to Vercel diff --git a/acceptance/Dockerfile b/acceptance/Dockerfile index a2283d09..36f6ba8f 100644 --- a/acceptance/Dockerfile +++ b/acceptance/Dockerfile @@ -1,6 +1,5 @@ FROM golang:1.19-alpine RUN apk add curl jq -RUN go install github.com/zitadel/zitadel-tools@v0.4.0 COPY setup.sh /setup.sh RUN chmod +x /setup.sh ENTRYPOINT [ "/setup.sh" ] diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index ac2fa512..59839e00 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.8" - services: zitadel: user: "${ZITADEL_DEV_UID}" @@ -8,24 +6,29 @@ services: ports: - "8080:8080" volumes: - - ./machinekey:/machinekey + - ./pat:/pat - ./zitadel.yaml:/zitadel.yaml depends_on: db: condition: "service_healthy" db: - image: "cockroachdb/cockroach:v22.2.2" - command: "start-single-node --insecure --http-addr :9090" + restart: 'always' + image: "${POSTGRES_IMAGE:-postgres:latest}" + environment: + - POSTGRES_USER=zitadel + - PGUSER=zitadel + - POSTGRES_DB=zitadel + - POSTGRES_HOST_AUTH_METHOD=trust + command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0 healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9090/health?ready=1"] - interval: "10s" - timeout: "30s" + test: ["CMD-SHELL", "pg_isready"] + interval: '10s' + timeout: '30s' retries: 5 - start_period: "20s" + start_period: '20s' ports: - - "26257:26257" - - "9090:9090" + - 5432:5432 wait_for_zitadel: image: curlimages/curl:8.00.1 @@ -33,7 +36,7 @@ services: [ "/bin/sh", "-c", - "i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 30 ] && exit 1 || exit 0", + "i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 120 ] && exit 1 || exit 0", ] depends_on: - zitadel @@ -43,11 +46,11 @@ services: container_name: setup build: . environment: - KEY: /key/zitadel-admin-sa.json - SERVICE: http://zitadel:8080 - WRITE_ENVIRONMENT_FILE: /apps/login/.env.acceptance + PAT_FILE: /pat/zitadel-admin-sa.pat + ZITADEL_API_INTERNAL_URL: http://zitadel:8080 + WRITE_ENVIRONMENT_FILE: /apps/login/.env.local volumes: - - "./machinekey:/key" + - "./pat:/pat" - "../apps/login:/apps/login" depends_on: wait_for_zitadel: diff --git a/acceptance/machinekey/.gitignore b/acceptance/machinekey/.gitignore deleted file mode 100644 index 7c9f54d0..00000000 --- a/acceptance/machinekey/.gitignore +++ /dev/null @@ -1 +0,0 @@ -zitadel-admin-sa.json diff --git a/acceptance/machinekey/.kitkeep b/acceptance/machinekey/.kitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/acceptance/pat/.gitignore b/acceptance/pat/.gitignore new file mode 100644 index 00000000..f0fa09f5 --- /dev/null +++ b/acceptance/pat/.gitignore @@ -0,0 +1,2 @@ +* +!.gitkeep \ No newline at end of file diff --git a/acceptance/machinekey/.gitkeep b/acceptance/pat/.gitkeep similarity index 100% rename from acceptance/machinekey/.gitkeep rename to acceptance/pat/.gitkeep diff --git a/acceptance/setup.sh b/acceptance/setup.sh index d3db4f25..5359659e 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -1,125 +1,34 @@ #!/bin/sh -set -e - -KEY=${KEY:-./machinekey/zitadel-admin-sa.json} -echo "Using key path ${KEY} to the instance admin service account." - -AUDIENCE=${AUDIENCE:-http://localhost:8080} -echo "Using audience ${AUDIENCE} for which the key is used." +set -ex + +PAT_FILE=${PAT_FILE:-./pat/zitadel-admin-sa.pat} +ZITADEL_API_PROTOCOL="${ZITADEL_API_PROTOCOL:-http}" +ZITADEL_API_DOMAIN="${ZITADEL_API_DOMAIN:-localhost}" +ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}" +ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}" +ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}" + +if [ -z "${PAT}" ]; then + echo "Reading PAT from file ${PAT_FILE}" + PAT=$(cat ${PAT_FILE}) +fi -SERVICE=${SERVICE:-$AUDIENCE} -echo "Using the service ${SERVICE} to connect to ZITADEL. For example in docker compose this can differ from the audience." +if [ -z "${ZITADEL_SERVICE_USER_ID}" ]; then + echo "Reading ZITADEL_SERVICE_USER_ID from userinfo endpoint" + USERINFO_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/oidc/v1/userinfo" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}") + echo "Received userinfo response: ${USERINFO_RESPONSE}" + ZITADEL_SERVICE_USER_ID=$(echo "${USERINFO_RESPONSE}" | jq --raw-output '.sub') +fi -WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.acceptance} +WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done." -AUDIENCE_HOST="$(echo $AUDIENCE | cut -d/ -f3)" -echo "Deferred the Host header ${AUDIENCE_HOST} which will be sent in requests that ZITADEL then maps to a virtual instance" - -JWT=$(zitadel-tools key2jwt --key ${KEY} --audience ${AUDIENCE}) -echo "Created JWT from Admin service account key ${JWT}" - -TOKEN_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/oauth/v2/token \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --header "Host: ${AUDIENCE_HOST}" \ - --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ - --data scope='openid profile email urn:zitadel:iam:org:project:id:zitadel:aud' \ - --data assertion="${JWT}") -echo "Got response from token endpoint:" -echo "${TOKEN_RESPONSE}" | jq - -TOKEN=$(echo -n ${TOKEN_RESPONSE} | jq --raw-output '.access_token') -echo "Extracted access token ${TOKEN}" - -ORG_RESPONSE=$(curl -s --request GET \ - --url ${SERVICE}/admin/v1/orgs/default \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}") -echo "Got default org response:" -echo "${ORG_RESPONSE}" | jq - -ORG_ID=$(echo -n ${ORG_RESPONSE} | jq --raw-output '.org.id') -echo "Extracted default org id ${ORG_ID}" - -echo "ZITADEL_API_URL=${AUDIENCE} -ZITADEL_ORG_ID=${ORG_ID} -ZITADEL_SERVICE_USER_TOKEN=${TOKEN}" > ${WRITE_ENVIRONMENT_FILE} +echo "ZITADEL_API_URL=${ZITADEL_API_URL} +ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID} +ZITADEL_SERVICE_USER_TOKEN=${PAT}" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} - -if ! grep -q 'localhost' ${WRITE_ENVIRONMENT_FILE}; then - echo "Not developing against localhost, so creating a human user might not be necessary" - exit 0 -fi - -HUMAN_USER_USERNAME="zitadel-admin@zitadel.localhost" -HUMAN_USER_PASSWORD="Password1!" - -HUMAN_USER_PAYLOAD=$(cat << EOM -{ - "userName": "${HUMAN_USER_USERNAME}", - "profile": { - "firstName": "ZITADEL", - "lastName": "Admin", - "displayName": "ZITADEL Admin", - "preferredLanguage": "en" - }, - "email": { - "email": "zitadel-admin@zitadel.localhost", - "isEmailVerified": true - }, - "password": "${HUMAN_USER_PASSWORD}", - "passwordChangeRequired": false -} -EOM -) -echo "Creating human user" -echo "${HUMAN_USER_PAYLOAD}" | jq - -HUMAN_USER_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/management/v1/users/human/_import \ - --header 'Content-Type: application/json' \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}" \ - --data-raw "${HUMAN_USER_PAYLOAD}") -echo "Create human user response" -echo "${HUMAN_USER_RESPONSE}" | jq - -if [ "$(echo -n "${HUMAN_USER_RESPONSE}" | jq --raw-output '.code')" == "6" ]; then - echo "admin user already exists" - exit 0 -fi - -HUMAN_USER_ID=$(echo -n ${HUMAN_USER_RESPONSE} | jq --raw-output '.userId') -echo "Extracted human user id ${HUMAN_USER_ID}" - -HUMAN_ADMIN_PAYLOAD=$(cat << EOM -{ - "userId": "${HUMAN_USER_ID}", - "roles": [ - "IAM_OWNER" - ] -} -EOM -) -echo "Granting iam owner to human user" -echo "${HUMAN_ADMIN_PAYLOAD}" | jq - -HUMAN_ADMIN_RESPONSE=$(curl -s --request POST \ - --url ${SERVICE}/admin/v1/members \ - --header 'Content-Type: application/json' \ - --header 'Accept: application/json' \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "Host: ${AUDIENCE_HOST}" \ - --data-raw "${HUMAN_ADMIN_PAYLOAD}") - -echo "Grant iam owner to human user response" -echo "${HUMAN_ADMIN_RESPONSE}" | jq - -echo "You can now log in at ${AUDIENCE}/ui/login" -echo "username: ${HUMAN_USER_USERNAME}" -echo "password: ${HUMAN_USER_PASSWORD}" \ No newline at end of file diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts new file mode 100644 index 00000000..31d16535 --- /dev/null +++ b/acceptance/tests/username-password.spec.ts @@ -0,0 +1,12 @@ +import { test } from "@playwright/test"; + +test("username and password", async ({ page }) => { + await page.goto("/"); + const loginname = page.getByLabel("Loginname"); + await loginname.pressSequentially("zitadel-admin@zitadel.localhost"); + await loginname.press("Enter"); + const password = page.getByLabel("Password"); + await password.pressSequentially("Password1!"); + await password.press("Enter"); + await page.getByRole("heading", { name: "Welcome ZITADEL Admin!" }).click(); +}); diff --git a/acceptance/tests/welcome.ts b/acceptance/tests/welcome.ts new file mode 100644 index 00000000..7ff6b7d1 --- /dev/null +++ b/acceptance/tests/welcome.ts @@ -0,0 +1,6 @@ +import { test } from "@playwright/test"; + +test("login is accessible", async ({ page }) => { + await page.goto("http://localhost:3000/"); + await page.getByRole("heading", { name: "Welcome back!" }).isVisible(); +}); diff --git a/acceptance/zitadel.yaml b/acceptance/zitadel.yaml index 2b471db0..407f424f 100644 --- a/acceptance/zitadel.yaml +++ b/acceptance/zitadel.yaml @@ -1,22 +1,41 @@ FirstInstance: - MachineKeyPath: /machinekey/zitadel-admin-sa.json + PatPath: /pat/zitadel-admin-sa.pat Org: + Human: + UserName: zitadel-admin + FirstName: ZITADEL + LastName: Admin + Password: Password1! + PasswordChangeRequired: false + PreferredLanguage: en Machine: Machine: Username: zitadel-admin-sa Name: Admin - MachineKey: - Type: 1 + Pat: + ExpirationDate: 2099-01-01T00:00:00Z Database: - Cockroach: + EventPushConnRatio: 0.2 # 4 + ProjectionSpoolerConnRatio: 0.3 # 6 + postgres: Host: db + Port: 5432 + Database: zitadel + MaxOpenConns: 20 + MaxIdleConns: 20 + MaxConnLifetime: 1h + MaxConnIdleTime: 5m + User: + Username: zitadel + SSL: + Mode: disable + Admin: + Username: zitadel + SSL: + Mode: disable Logstore: Access: Stdout: Enabled: true - -DefaultInstance: - LoginPolicy: - MfaInitSkipLifetime: 0h \ No newline at end of file diff --git a/apps/login/package.json b/apps/login/package.json index 9617300a..54176632 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -24,6 +24,7 @@ "build": "next build", "prestart": "pnpm build", "start": "next start", + "start:built": "next start", "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, "git": { diff --git a/package.json b/package.json index 4ec7f249..6b5e27cc 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,22 @@ "generate": "turbo run generate", "build": "turbo run build", "test": "turbo run test", + "start": "turbo run start", + "start:built": "turbo run start:built", "test:unit": "turbo run test:unit -- --passWithNoTests", "test:integration": "turbo run test:integration", + "test:acceptance": "pnpm exec playwright test", "test:watch": "turbo run test:watch", "dev": "turbo run dev --no-cache --continue", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "clean": "turbo run clean && rm -rf node_modules", + "format:fix": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --check \"**/*.{ts,tsx,md}\"", "changeset": "changeset", "version-packages": "changeset version", - "release": "turbo run build --filter=login^... && changeset publish" + "release": "turbo run build --filter=login^... && changeset publish", + "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup" }, "pnpm": { "overrides": { @@ -25,10 +30,12 @@ }, "devDependencies": { "@changesets/cli": "^2.27.8", + "@playwright/test": "^1.48.1", + "@types/node": "^22.7.5", "@vitejs/plugin-react": "^4.2.1", + "@zitadel/prettier-config": "workspace:*", "eslint": "8.57.1", "eslint-config-zitadel": "workspace:*", - "@zitadel/prettier-config": "workspace:*", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "tsup": "^8.3.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..bf73cb21 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,81 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./acceptance/tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://127.0.0.1:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + /* TODO: webkit fails. Is this a bug? + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, +*/ + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run local dev server before starting the tests */ + webServer: { + command: "pnpm start:built", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + timeout: 5 * 60_000, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a2f4ff9..8afc1642 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: '@changesets/cli': specifier: ^2.27.8 version: 2.27.8 + '@playwright/test': + specifier: ^1.48.1 + version: 1.48.1 + '@types/node': + specifier: ^22.7.5 + version: 22.7.6 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + version: 4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) '@zitadel/prettier-config': specifier: workspace:* version: link:packages/zitadel-prettier-config @@ -43,10 +49,10 @@ importers: version: 5.6.2 vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + version: 5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1) + version: 2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1) apps/login: dependencies: @@ -61,7 +67,7 @@ importers: version: 0.5.7(tailwindcss@3.4.13) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -85,13 +91,13 @@ importers: version: 2.30.1 next: specifier: 14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + version: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) next-intl: specifier: ^3.20.0 - version: 3.20.0(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) + version: 3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -1036,6 +1042,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.48.1': + resolution: {integrity: sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==} + engines: {node: '>=18'} + hasBin: true + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -1270,6 +1281,9 @@ packages: '@types/node@22.5.5': resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/node@22.7.6': + resolution: {integrity: sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2403,6 +2417,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3473,6 +3492,16 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + playwright-core@1.48.1: + resolution: {integrity: sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.48.1: + resolution: {integrity: sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -5375,6 +5404,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.48.1': + dependencies: + playwright: 1.48.1 + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -5589,6 +5622,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/node@22.7.6': + dependencies: + undici-types: 6.19.8 + '@types/normalize-package-data@2.4.4': {} '@types/prop-types@15.7.12': {} @@ -5614,7 +5651,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.6 optional: true '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': @@ -5659,23 +5696,23 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 '@vercel/git-hooks@1.0.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': + '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - supports-color @@ -5686,13 +5723,13 @@ snapshots: chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1))': + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1))': dependencies: '@vitest/spy': 2.1.1 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) '@vitest/pretty-format@2.1.1': dependencies: @@ -7007,6 +7044,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7792,21 +7832,21 @@ snapshots: negotiator@0.6.3: {} - next-intl@3.20.0(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1): + next-intl@3.20.0(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 use-intl: 3.20.0(react@18.3.1) - next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next: 14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): + next@14.2.14(@babel/core@7.25.2)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1): dependencies: '@next/env': 14.2.14 '@swc/helpers': 0.5.5 @@ -7827,6 +7867,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.14 '@next/swc-win32-ia32-msvc': 14.2.14 '@next/swc-win32-x64-msvc': 14.2.14 + '@playwright/test': 1.48.1 sass: 1.79.1 transitivePeerDependencies: - '@babel/core' @@ -8065,6 +8106,14 @@ snapshots: pirates@4.0.6: {} + playwright-core@1.48.1: {} + + playwright@1.48.1: + dependencies: + playwright-core: 1.48.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.0.0: {} postcss-import@15.1.0(postcss@8.4.47): @@ -8163,7 +8212,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.5.5 + '@types/node': 22.7.6 long: 5.2.3 proxy-from-env@1.0.0: {} @@ -9017,12 +9066,12 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.1.1(@types/node@22.5.5)(sass@1.79.1): + vite-node@2.1.1(@types/node@22.7.6)(sass@1.79.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -9034,31 +9083,31 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)): + vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)): dependencies: debug: 4.3.6 globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.6.2) optionalDependencies: - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.6(@types/node@22.5.5)(sass@1.79.1): + vite@5.4.6(@types/node@22.7.6)(sass@1.79.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.21.3 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.6 fsevents: 2.3.3 sass: 1.79.1 - vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(sass@1.79.1): + vitest@2.1.1(@types/node@22.7.6)(jsdom@25.0.0)(sass@1.79.1): dependencies: '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)(sass@1.79.1)) + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.7.6)(sass@1.79.1)) '@vitest/pretty-format': 2.1.1 '@vitest/runner': 2.1.1 '@vitest/snapshot': 2.1.1 @@ -9073,11 +9122,11 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.5.5)(sass@1.79.1) - vite-node: 2.1.1(@types/node@22.5.5)(sass@1.79.1) + vite: 5.4.6(@types/node@22.7.6)(sass@1.79.1) + vite-node: 2.1.1(@types/node@22.7.6)(sass@1.79.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.6 jsdom: 25.0.0 transitivePeerDependencies: - less diff --git a/turbo.json b/turbo.json index 7d38ae4c..a98ff872 100644 --- a/turbo.json +++ b/turbo.json @@ -21,6 +21,8 @@ }, "build": {}, "test": {}, + "start": {}, + "start:built": {}, "test:unit": {}, "test:integration": {}, "test:watch": {