diff --git a/.dockerignore b/.dockerignore index e7203924a6..1a5fa562cb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,9 @@ public/dist /playwright/.cache/ /out /docker + +Makefile +docker-bake.hcl +*.md +.gitignore +scripts diff --git a/Makefile b/Makefile index 3b617e4e4d..12f7a7eff0 100644 --- a/Makefile +++ b/Makefile @@ -1,73 +1,102 @@ -LOGIN_BASE_TAG ?= "zitadel-login-base:local" +LOGIN_DEPENDENCIES_TAG ?= "zitadel-login-dev-dependencies:local" +LOGIN_IMAGE_TAG ?= "zitadel-login:local" CORE_MOCK_TAG ?= "zitadel-core-mock:local" +LOGIN_INTEGRATION_TESTSUITE_TAG ?= "zitadel-login-integration-testsuite:local" +CORE_MOCK_CONTAINER_NAME ?= zitadel-mock-grpc-server +LOGIN_CONTAINER_NAME ?= zitadel-login + XDG_CACHE_HOME ?= $(HOME)/.cache -CACHE_DIR ?= $(XDG_CACHE_HOME)/zitadel-make +export CACHE_DIR ?= $(XDG_CACHE_HOME)/zitadel-make .PHONY: help help: @echo "Makefile for the login service" @echo "Available targets:" - @echo " help - Show this help message" - @echo " lint - Run linting and formatting checks" - @echo " lint-force - Force run linting and formatting checks" - @echo " unit - Run unit tests" - @echo " unit-force - Force run unit tests" - @echo " integration - Run integration tests" - @echo " integration-force - Force run integration tests" - @echo " login-image - Build the login image" - @echo " quality - Run all quality checks (lint, unit, integration)" - @echo " ci - Run all CI tasks. Run it with the -j flag to parallelize. make -j ci" + @echo " help - Show this help message" + @echo " login - Start the login service" + @echo " login-lint - Run linting and formatting checks" + @echo " login-lint-force - Force run linting and formatting checks" + @echo " login-unit - Run unit tests" + @echo " login-unit-force - Force run unit tests" + @echo " login-integration - Run integration tests" + @echo " login-integration-force - Force run integration tests" + @echo " login-image - Build the login image" + @echo " login-quality - Run all quality checks (login-lint, unit, integration)" + @echo " login-ci - Run all CI tasks. Run it with the -j flag to parallelize. make -j ci" + @echo " show-cache - Show cached digests and exit codes" + @echo " clean-cache - Remove the cache directory" + @echo " core-mock - Start the core mock server" + @echo " core-mock-stop - Stop the core mock server" -.PHONY: lint-force -lint-force: - docker run --rm $(LOGIN_BASE_TAG) lint - docker run --rm $(LOGIN_BASE_TAG) format --check -.PHONY: lint -lint: - $(call run_or_skip,lint-force,lint,$(LOGIN_BASE_TAG)) +.PHONY: login-lint-force +login-lint-force: login-dev-dependencies + docker run --rm $(LOGIN_DEPENDENCIES_TAG) lint + docker run --rm $(LOGIN_DEPENDENCIES_TAG) format --check -unit-run: login-base - docker run --rm $(LOGIN_BASE_TAG) test:unit +.PHONY: login-lint +login-lint: + ./scripts/run_or_skip.sh login-lint-force $(LOGIN_DEPENDENCIES_TAG) -.PHONY: unit-force -unit-force: - docker run --rm $(LOGIN_BASE_TAG) test:unit +.PHONY: login-unit-force +login-unit-force: login-dev-dependencies + docker run --rm $(LOGIN_DEPENDENCIES_TAG) test:unit -.PHONY: unit -unit: - $(call run_or_skip,unit-force,unit,$(LOGIN_BASE_TAG)) +.PHONY: login-unit +login-unit: + ./scripts/run_or_skip.sh login-unit-force $(LOGIN_DEPENDENCIES_TAG) -.PHONY: integration-force -integration-force: - docker run --rm $(CORE_MOCK_TAG) test:integration +.PHONY: login-integration-force +login-integration-force: login core-mock login-integration-testsuite + docker run --rm $(LOGIN_INTEGRATION_TESTSUITE_TAG) + $(MAKE) core-mock-stop -.PHONY: integration -integration: - $(call run_or_skip,integration-force,integration,$(CORE_MOCK_TAG)) +.PHONY: login-integration +login-integration: + ./scripts/run_or_skip.sh login-integration-force '$(LOGIN_DEPENDENCIES_TAG);$(CORE_MOCK_TAG);$(LOGIN_INTEGRATION_TESTSUITE_TAG)' + +.PHONY: login-quality +login-quality: core-mock-build login-quality-after-build +login-quality-after-build: login-lint login-unit login-integration + @: + +.PHONY: login-ci +login-ci: core-mock-build login-ci-after-build +login-ci-after-build: login-quality-after-build login-image + @: + +login-dev-dependencies: + docker buildx bake login-dev-dependencies --set login-dev-dependencies.tags=$(LOGIN_DEPENDENCIES_TAG); .PHONY: login-image login-image: - docker buildx bake login-image + docker buildx bake login-image --set login-image.tags=$(LOGIN_IMAGE_TAG); -.PHONY: quality -quality: lint unit integration +.PHONY: login +login: login-image login-stop + docker run --detach --rm --name $(LOGIN_CONTAINER_NAME) --publish 3000:3000 $(LOGIN_IMAGE_TAG) -.PHONY: ci -ci: core-mock ci-after-build -ci-after-build: quality login-image - @: +login-stop: + docker rm --force $(LOGIN_CONTAINER_NAME) 2>/dev/null || true -login-base: - docker buildx bake login-base --set login-base.tags=$(LOGIN_BASE_TAG); +core-mock-build: + docker buildx bake core-mock --set core-mock.tags=$(CORE_MOCK_TAG); -core-mock: - docker buildx bake core-mock --set login-base.tags=$(CORE_MOCK_TAG); +login-integration-testsuite: login-dev-dependencies + docker buildx bake login-integration-testsuite --set login-integration-testsuite.tags=$(LOGIN_INTEGRATION_TESTSUITE_TAG) + +.PHONY: core-mock +core-mock: core-mock-build core-mock-stop + docker run --detach --rm --name $(CORE_MOCK_CONTAINER_NAME) --publish 22221:22221 --publish 22222:22222 $(CORE_MOCK_TAG) + +.PHONY: core-mock-stop +core-mock-stop: + docker rm --force $(CORE_MOCK_CONTAINER_NAME) 2>/dev/null || true .PHONY: clean-cache clean-cache: @echo "Removing cache directory: $(CACHE_DIR)" - @rm -rf "$(CACHE_DIR)" + rm -rf "$(CACHE_DIR)" .PHONY: show-cache show-cache: @@ -75,30 +104,3 @@ show-cache: @find "$(CACHE_DIR)" -type f 2>/dev/null | while read file; do \ echo "$$file: $$(cat $$file)"; \ done - -# run_or_skip: runs a task only if the Docker image has changed and caches the result -# $(1): Taskname (e.g. "lint-force") -# $(2): Cache-ID (e.g. "lint") -# $(3): Docker-Image (e.g. "zitadel-login-base:local") -define run_or_skip - @digest_file="$(CACHE_DIR)/$(2).$(3)"; \ - mkdir -p $(CACHE_DIR); \ - if [ -f "$$digest_file" ]; then \ - digest_before=$$(cut -d',' -f1 "$$digest_file"); \ - status_before=$$(cut -d',' -f2 "$$digest_file"); \ - else \ - digest_before=""; \ - status_before=1; \ - fi; \ - current_digest=$$(docker image inspect $(3) --format='{{.Id}}'); \ - if [ "$$digest_before" = "$$current_digest" ]; then \ - echo "Skipping $(1) – image unchanged, returning cached status $$status_before"; \ - exit $$status_before; \ - else \ - echo "Running $(1)..."; \ - $(MAKE) $(1); \ - status=$$?; \ - echo "$$current_digest,$$status" > "$$digest_file"; \ - exit $$status; \ - fi -endef diff --git a/apps/login/cypress/Dockerfile b/apps/login/cypress/Dockerfile new file mode 100644 index 0000000000..65b7644123 --- /dev/null +++ b/apps/login/cypress/Dockerfile @@ -0,0 +1,6 @@ +FROM cypress/factory AS login-integration-testsuite +WORKDIR /opt/app +COPY --from=login-dev-dependencies /app/apps/login/node_modules . +RUN npx cypress install +COPY . . +CMD ["npx", "cypress", "run"] diff --git a/apps/login/cypress/cypress.config.ts b/apps/login/cypress/cypress.config.ts index ed880fb48c..855eda22e1 100644 --- a/apps/login/cypress/cypress.config.ts +++ b/apps/login/cypress/cypress.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ reporter: "list", e2e: { baseUrl: "http://localhost:3000", - specPattern: "cypress/integration/**/*.cy.{js,jsx,ts,tsx}", + specPattern: "integration/**/*.cy.{js,jsx,ts,tsx}", setupNodeEvents(on, config) { // implement node event listeners here }, diff --git a/apps/login/cypress/package.json b/apps/login/cypress/package.json new file mode 100644 index 0000000000..28cfe25020 --- /dev/null +++ b/apps/login/cypress/package.json @@ -0,0 +1,22 @@ +{ + "name": "login-integration-testsuite", + "private": true, + "scripts": { + "test:integration": "pnpm exec concurrently --names 'mock,test' --success command-test --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test start http://localhost:3000 \"test:integration:run\"'", + "test:integration:watch:run": "pnpm exec concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'", + "test:integration:watch:open": "pnpm exec concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'", + "test:integration:run": "pnpm exec cypress run --config-file ./cypress/cypress.config.ts --quiet", + "test:integration:open": "pnpm exec cypress open --config-file ./cypress/cypress.config.ts", + "mock": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 ${CORE_MOCK_TAG:-zitadel-core-mock:local}", + "mock:stop": "docker rm --force zitadel-mock-grpc-server 2>/dev/null || true" + }, + "devDependencies": { + "@types/node": "^22.14.1", + "concurrently": "^9.1.2", + "cypress": "^14.3.2", + "env-cmd": "^10.0.0", + "nodemon": "^3.1.9", + "start-server-and-test": "^2.0.11", + "typescript": "^5.8.3" + } +} diff --git a/apps/login/cypress/turbo.json b/apps/login/cypress/turbo.json new file mode 100644 index 0000000000..6bc514b24c --- /dev/null +++ b/apps/login/cypress/turbo.json @@ -0,0 +1,14 @@ +{ + "extends": ["///"], + "tasks": { + "test": { + "dependsOn": ["@zitadel/client#build"] + }, + "test:integration": { + "dependsOn": ["@zitadel/client#build"] + }, + "test:integration:run": { + "dependsOn": ["@zitadel/client#build"] + } + } +} diff --git a/dockerfiles/core-mock.Dockerfile b/apps/login/mock/Dockerfile similarity index 50% rename from dockerfiles/core-mock.Dockerfile rename to apps/login/mock/Dockerfile index 8033e8b211..fff98a50c7 100644 --- a/dockerfiles/core-mock.Dockerfile +++ b/apps/login/mock/Dockerfile @@ -1,13 +1,9 @@ -FROM scratch AS config - -COPY mocked-services.cfg . -COPY initial-stubs initial-stubs -COPY --from=protos /proto . - FROM golang:1.20.5-alpine3.18 RUN go install github.com/eliobischof/grpc-mock/cmd/grpc-mock@01b09f60db1b501178af59bed03b2c22661df48c -COPY --from=config / . +COPY mocked-services.cfg . +COPY initial-stubs initial-stubs +COPY --from=protos . . -ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -proto $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] +ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -protos $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] diff --git a/apps/login/package.json b/apps/login/package.json index d9573db8dd..a146ea2114 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -5,29 +5,19 @@ "scripts": { "dev": "pnpm exec next dev --turbopack", "test": "pnpm exec concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", - "test:watch": "cpnpm exec oncurrently --kill-others 'npm:test:unit:watch' 'npm:test:integration:watch'", + "test:watch": "pnpm exec oncurrently --kill-others 'npm:test:unit:watch' 'npm:test:integration:watch'", "test:unit": "pnpm exec vitest", "test:unit:watch": "pnpm test:unit --watch", - "test:integration": "pnpm mock:build && pnpm exec concurrently --names 'mock,test' --success command-test --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test start http://localhost:3000 \"test:integration:run\"'", - "test:integration:watch:run": "pnpm exec concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'", - "test:integration:watch:open": "pnpm exec concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'", - "test:integration:run": "pnpm exec cypress run --config-file ./cypress/cypress.config.ts --quiet", - "test:integration:open": "pnpm exec cypress open --config-file ./cypress/cypress.config.ts", - "mock": "pnpm mock:build && pnpm mock:run", - "mock:run": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 zitadel-mock-grpc-server", - "mock:build": "DOCKER_BUILDKIT=1 docker build --tag zitadel-mock-grpc-server ./mock", - "mock:build:nocache": "pnpm mock:build --no-cache", + "mock": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 ${CORE_MOCK_TAG:-zitadel-core-mock:local}", "mock:stop": "docker rm --force zitadel-mock-grpc-server 2>/dev/null || true", - "mock:destroy": "docker rmi --force zitadel-mock-grpc-server 2>/dev/null || true", "lint": "pnpm exec next lint && pnpm exec prettier --check .", "lint:fix": "pnpm exec prettier --write .", "lint-staged": "lint-staged", "build": "pnpm exec next build", "build:standalone": "NEXT_OUTPUT_MODE=standalone pnpm build", - "prestart": "pnpm build", - "start": "pnpm exec next start", + "start": "pnpm build && pnpm exec next start", "start:built": "pnpm exec next start", - "clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next" + "clean": "pnpm mock:stop && rm -rf .turbo && rm -rf node_modules && rm -rf .next" }, "git": { "pre-commit": "lint-staged" @@ -77,18 +67,13 @@ "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.21", "concurrently": "^9.1.2", - "cypress": "^14.3.2", - "del-cli": "6.0.0", - "env-cmd": "^10.0.0", "grpc-tools": "1.13.0", "jsdom": "^26.1.0", "lint-staged": "15.5.1", "make-dir-cli": "4.0.0", - "nodemon": "^3.1.9", "postcss": "8.5.3", "prettier-plugin-tailwindcss": "0.6.11", "sass": "^1.87.0", - "start-server-and-test": "^2.0.11", "tailwindcss": "3.4.14", "ts-proto": "^2.7.0", "typescript": "^5.8.3" diff --git a/apps/login/turbo.json b/apps/login/turbo.json index 80224125a2..60906c0d37 100644 --- a/apps/login/turbo.json +++ b/apps/login/turbo.json @@ -6,15 +6,11 @@ "dependsOn": ["^build"] }, "build:standalone": { - "outputs": ["dist/**", ".next/**", "!.next/cache/**"], - "dependsOn": ["^build"] + "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }, "test": { "dependsOn": ["@zitadel/client#build"] }, - "test:integration": { - "dependsOn": ["@zitadel/client#build"] - }, "test:unit": { "dependsOn": ["@zitadel/client#build"] }, diff --git a/docker-bake.hcl b/docker-bake.hcl index 5f5c59788b..01f8f41f26 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,44 +1,65 @@ -variable "release_tags" { - default = ["zitadel-login:local"] -} - group "default" { - targets = ["login-generate"] + targets = ["typescript-proto-client"] } -target "login-base" { - context = "." - dockerfile = "dockerfiles/login-base.Dockerfile" +target "login-platform" { + dockerfile = "dockerfiles/login-platform.Dockerfile" } -target "download-protos" { - dockerfile = "dockerfiles/download-protos.Dockerfile" - contexts = { - base = "target:login-base" - } +target "login-dev-base" { + dockerfile = "dockerfiles/login-dev-base.Dockerfile" + contexts = { + login-platform = "target:login-platform" + } +} + +target "login-dev-dependencies" { + dockerfile = "dockerfiles/login-dev-dependencies.Dockerfile" + contexts = { + login-dev-base = "target:login-dev-base" + } +} + +# proto-files is only used to build core-mock against which the integration tests run. +# To build the proto-client, we use buf to generate and download the client code directly. +target "proto-files" { + dockerfile = "dockerfiles/proto-files.Dockerfile" + contexts = { + login-dev-base = "target:login-dev-dependencies" + } } target "core-mock" { - dockerfile = "dockerfiles/core-mock.Dockerfile" + context = "apps/login/mock" + dockerfile = "Dockerfile" contexts = { - protos = "target:download-protos" + protos = "target:proto-files" } } -target "login-generate" { - dockerfile = "dockerfiles/login-generate.Dockerfile" +target "login-integration-testsuite" { + context = "apps/login/cypress" contexts = { - base = "target:login-base" + login-dev-dependencies = "target:login-dev-dependencies" } } +target "typescript-proto-client" { + dockerfile = "dockerfiles/typescript-proto-client.Dockerfile" + contexts = { + # We directly generate and download the client server-side with buf, so we don't need the proto files + login-dev-base = "target:login-dev-dependencies" + } +} + +# We run integration and acceptance tests against the next standalone server for docker. target "login-image" { dockerfile = "dockerfiles/login-image.Dockerfile" - tags = "${release_tags}" args = { NODE_ENV = "production" } contexts = { - generated = "target:login-generate" + login-platform = "target:login-platform" + login-dev-base = "target:login-dev-dependencies" } } diff --git a/dockerfiles/download-protos.Dockerfile b/dockerfiles/download-protos.Dockerfile deleted file mode 100644 index d83e1b0eff..0000000000 --- a/dockerfiles/download-protos.Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM base - -RUN cd packages/zitadel-proto && \ - pnpm buf export https://github.com/envoyproxy/protoc-gen-validate.git --path validate --output /proto && \ - pnpm buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto && \ - pnpm buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto && \ - pnpm buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /proto diff --git a/dockerfiles/login-dev-base.Dockerfile b/dockerfiles/login-dev-base.Dockerfile new file mode 100644 index 0000000000..08f657c53d --- /dev/null +++ b/dockerfiles/login-dev-base.Dockerfile @@ -0,0 +1,10 @@ +FROM login-platform AS login-dev-base + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable + +RUN apk add --no-cache libc6-compat bash git + +WORKDIR /app diff --git a/dockerfiles/login-base.Dockerfile b/dockerfiles/login-dev-dependencies.Dockerfile similarity index 81% rename from dockerfiles/login-base.Dockerfile rename to dockerfiles/login-dev-dependencies.Dockerfile index ee7dea7469..d335aeb4a3 100644 --- a/dockerfiles/login-base.Dockerfile +++ b/dockerfiles/login-dev-dependencies.Dockerfile @@ -1,13 +1,4 @@ -FROM node:20-alpine AS base - -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" - -RUN corepack enable - -RUN apk add --no-cache libc6-compat bash git - -WORKDIR /app +FROM login-dev-base AS login-dev-dependencies COPY \ turbo.json \ @@ -25,10 +16,9 @@ COPY packages/zitadel-proto/package.json ./packages/zitadel-proto/ COPY packages/zitadel-tailwind-config/package.json ./packages/zitadel-tailwind-config/ COPY packages/zitadel-tsconfig/package.json ./packages/zitadel-tsconfig/ COPY apps/login/package.json ./apps/login/ +COPY apps/login/cypress/package.json ./apps/login/cypress/ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile -COPY . . - ENTRYPOINT ["pnpm"] diff --git a/dockerfiles/login-generate.Dockerfile b/dockerfiles/login-generate.Dockerfile deleted file mode 100644 index 4ea4ddf59a..0000000000 --- a/dockerfiles/login-generate.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM base AS generated - -RUN pnpm generate diff --git a/dockerfiles/login-image.Dockerfile b/dockerfiles/login-image.Dockerfile index a67fb54327..6966029497 100644 --- a/dockerfiles/login-image.Dockerfile +++ b/dockerfiles/login-image.Dockerfile @@ -1,8 +1,20 @@ -FROM generated AS build-for-docker +FROM login-dev-base AS prune-for-docker -RUN NEXT_PUBLIC_BASE_PATH=/ui/v2/login pnpm build:docker +RUN pnpm install turbo --global -FROM node:20-alpine +COPY . . +RUN turbo prune @zitadel/login --docker + +FROM login-dev-base AS installer + +COPY --from=prune-for-docker /app/out/json/ . +RUN pnpm install --frozen-lockfile +COPY --from=prune-for-docker /app/out/full/ . +RUN NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone pnpm exec turbo run build + +RUN ls -la /app/apps/login/.next + +FROM login-platform AS login-image WORKDIR /app @@ -12,9 +24,9 @@ RUN addgroup --system --gid 1001 nodejs && \ # If /.env-file/.env is mounted into the container, its variables are made available to the server before it starts up. RUN mkdir -p /.env-file && touch /.env-file/.env && chown -R nextjs:nodejs /.env-file -COPY --chown=nextjs:nodejs --from=build-for-docker /app/docker/apps/login/.next/standalone ./ -COPY --chown=nextjs:nodejs --from=build-for-docker /app/docker/apps/login/.next/static ./apps/login/.next/static -COPY --chown=nextjs:nodejs --from=build-for-docker /app/docker/apps/login/public ./apps/login/public +COPY --chown=nextjs:nodejs --from=installer /app/apps/login/.next/standalone ./ +COPY --chown=nextjs:nodejs --from=installer /app/apps/login/.next/static ./apps/login/.next/static +COPY --chown=nextjs:nodejs --from=installer /app/apps/login/public ./apps/login/public USER nextjs ENV HOSTNAME="0.0.0.0" diff --git a/dockerfiles/login-platform.Dockerfile b/dockerfiles/login-platform.Dockerfile new file mode 100644 index 0000000000..74bb5c9546 --- /dev/null +++ b/dockerfiles/login-platform.Dockerfile @@ -0,0 +1 @@ +FROM node:20-alpine AS login-platform diff --git a/dockerfiles/proto-files.Dockerfile b/dockerfiles/proto-files.Dockerfile new file mode 100644 index 0000000000..adf78358e6 --- /dev/null +++ b/dockerfiles/proto-files.Dockerfile @@ -0,0 +1,10 @@ +FROM bufbuild/buf:1.54.0 AS proto-files + +RUN buf export https://github.com/envoyproxy/protoc-gen-validate.git --path validate --output /proto-files && \ + buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto-files && \ + buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto-files && \ + buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /proto-files + +FROM scratch + +COPY --from=proto-files /proto-files / diff --git a/dockerfiles/typescript-proto-client.Dockerfile b/dockerfiles/typescript-proto-client.Dockerfile new file mode 100644 index 0000000000..79dbdd65e0 --- /dev/null +++ b/dockerfiles/typescript-proto-client.Dockerfile @@ -0,0 +1,9 @@ +FROM login-dev-base AS zitadel-proto + +COPY packages/zitadel-proto packages/zitadel-proto + +RUN pnpm generate + +FROM scratch + +COPY --from=zitadel-proto /app/packages/zitadel-proto / diff --git a/package.json b/package.json index 4d2ee088ab..dd6a56f5af 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "start:built": "pnpm exec turbo run start:built", "test:unit": "pnpm exec turbo run test:unit -- --passWithNoTests", "test:integration": "pnpm exec turbo run test:integration", + "test:integration:run": "pnpm exec turbo run test:integration:run", "test:acceptance": "pnpm exec playwright test", "test:watch": "pnpm exec turbo run test:watch", "dev": "pnpm exec turbo run dev --no-cache --continue", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46a448c2f1..81e3dfe83b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,15 +192,6 @@ importers: concurrently: specifier: ^9.1.2 version: 9.1.2 - cypress: - specifier: ^14.3.2 - version: 14.3.2 - del-cli: - specifier: 6.0.0 - version: 6.0.0 - env-cmd: - specifier: ^10.0.0 - version: 10.1.0 grpc-tools: specifier: 1.13.0 version: 1.13.0 @@ -213,9 +204,6 @@ importers: make-dir-cli: specifier: 4.0.0 version: 4.0.0 - nodemon: - specifier: ^3.1.9 - version: 3.1.9 postcss: specifier: 8.5.3 version: 8.5.3 @@ -225,9 +213,6 @@ importers: sass: specifier: ^1.87.0 version: 1.87.0 - start-server-and-test: - specifier: ^2.0.11 - version: 2.0.11 tailwindcss: specifier: 3.4.14 version: 3.4.14 @@ -238,6 +223,30 @@ importers: specifier: ^5.8.3 version: 5.8.3 + apps/login/cypress: + devDependencies: + '@types/node': + specifier: ^22.14.1 + version: 22.14.1 + concurrently: + specifier: ^9.1.2 + version: 9.1.2 + cypress: + specifier: ^14.3.2 + version: 14.3.2 + env-cmd: + specifier: ^10.0.0 + version: 10.1.0 + nodemon: + specifier: ^3.1.9 + version: 3.1.9 + start-server-and-test: + specifier: ^2.0.11 + version: 2.0.11 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + packages/zitadel-client: dependencies: '@bufbuild/protobuf': @@ -1344,10 +1353,6 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -2130,15 +2135,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - del-cli@6.0.0: - resolution: {integrity: sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==} - engines: {node: '>=18'} - hasBin: true - - del@8.0.0: - resolution: {integrity: sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==} - engines: {node: '>=18'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2741,10 +2737,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@14.0.2: - resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} - engines: {node: '>=18'} - globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -2981,18 +2973,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-cwd@3.0.0: - resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -3578,10 +3562,6 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - p-map@7.0.2: - resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} - engines: {node: '>=18'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3626,10 +3606,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4110,10 +4086,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -4535,10 +4507,6 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -5798,8 +5766,6 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@sindresorhus/merge-streams@2.3.0': {} - '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -6689,20 +6655,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - del-cli@6.0.0: - dependencies: - del: 8.0.0 - meow: 13.2.0 - - del@8.0.0: - dependencies: - globby: 14.0.2 - is-glob: 4.0.3 - is-path-cwd: 3.0.0 - is-path-inside: 4.0.0 - p-map: 7.0.2 - slash: 5.1.0 - delayed-stream@1.0.0: {} delegates@1.0.0: {} @@ -6988,7 +6940,7 @@ snapshots: debug: 4.4.0(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -7001,7 +6953,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -7022,7 +6974,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -7520,15 +7472,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@14.0.2: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.2 - ignore: 5.3.2 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 - globrex@0.1.2: {} gopd@1.0.1: @@ -7745,12 +7688,8 @@ snapshots: is-number@7.0.0: {} - is-path-cwd@3.0.0: {} - is-path-inside@3.0.3: {} - is-path-inside@4.0.0: {} - is-potential-custom-element-name@1.0.1: {} is-regex@1.1.4: @@ -8336,8 +8275,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@7.0.2: {} - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -8371,8 +8308,6 @@ snapshots: path-type@4.0.0: {} - path-type@5.0.0: {} - pathe@2.0.3: {} pathval@2.0.0: {} @@ -8822,8 +8757,6 @@ snapshots: slash@3.0.0: {} - slash@5.1.0: {} - slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -9291,8 +9224,6 @@ snapshots: undici-types@6.21.0: {} - unicorn-magic@0.1.0: {} - universalify@0.1.2: {} universalify@2.0.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3ff5faaaf5..f573a5fb1d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - "apps/*" + - "apps/login/cypress" - "packages/*" diff --git a/scripts/run_or_skip.sh b/scripts/run_or_skip.sh new file mode 100755 index 0000000000..ad238039e9 --- /dev/null +++ b/scripts/run_or_skip.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Usage: ./run_or_skip.sh +# Example: ./run_or_skip.sh lint-force "img1;img2" + +set -euo pipefail + +if [ -z "$CACHE_DIR" ]; then + echo "CACHE_DIR is not set. Please set it to a valid directory." + exit 1 +fi + +MAKE_TARGET=$1 +IMAGES=$2 + +DIGEST_FILE="$CACHE_DIR/$MAKE_TARGET.digests" +mkdir -p "$CACHE_DIR" + +get_image_ids() { + local ids="" + for img in $(echo "$IMAGES" | tr ';' ' '); do + local id=$(docker image inspect "$img" --format='{{.Id}}' 2>/dev/null || true) + id=${id:-new-or-error} + ids="${ids}${id};" + done + ids=${ids%;} # Remove trailing semicolon + echo "$ids" +} + +OLD_DIGEST=$(cat "$DIGEST_FILE" 2>/dev/null || echo "") +OLD_STATUS=$(echo "$OLD_DIGEST" | cut -d ';' -f1) +OLD_IDS=$(echo "$OLD_DIGEST" | cut -d ';' -f2-9) +if [[ "$OLD_IDS" == "$(get_image_ids)" ]]; then + echo "Skipping $MAKE_TARGET – all images unchanged, returning cached status $OLD_STATUS" + exit $OLD_STATUS +else + echo "Running $MAKE_TARGET..." + set +e + make $MAKE_TARGET + STATUS=$? + set -e + echo "${STATUS};$(get_image_ids)" > $DIGEST_FILE + exit $STATUS +fi diff --git a/turbo.json b/turbo.json index d26bdd8f56..51ba3ec529 100644 --- a/turbo.json +++ b/turbo.json @@ -26,6 +26,7 @@ "start:built": {}, "test:unit": {}, "test:integration": {}, + "test:integration:run": {}, "test:watch": { "persistent": true },