From 3d4c608187a49773f021cd78dd23d544ad55ecce Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Mon, 20 Oct 2025 15:24:47 -0700 Subject: [PATCH] Customizable ports (#962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## High-level PR Summary This PR changes the default development ports for several background services to avoid conflicts. PostgreSQL moves from port `5432` to `8128`, Inbucket SMTP from `2500` to `8129`, Inbucket POP3 from `1100` to `8130`, and the OpenTelemetry collector from `4318` to `8131`. All references across configuration files, Docker Compose setups, environment files, CI/CD workflows, test files, and documentation have been updated to reflect these new port assignments. A knowledge base document has been added to document the new port mappings. ⏱️ Estimated Review Time: 15-30 minutes
💡 Review Order Suggestion | Order | File Path | | --- | --- | | 1 | `claude/CLAUDE-KNOWLEDGE.md` | | 2 | `apps/dev-launchpad/public/index.html` | | 3 | `docker/dependencies/docker.compose.yaml` | | 4 | `docker/emulator/docker.compose.yaml` | | 5 | `apps/backend/.env` | | 6 | `apps/backend/.env.development` | | 7 | `docker/server/.env.example` | | 8 | `package.json` | | 9 | `.devcontainer/devcontainer.json` | | 10 | `apps/e2e/.env.development` | | 11 | `.github/workflows/check-prisma-migrations.yaml` | | 12 | `.github/workflows/docker-server-test.yaml` | | 13 | `.github/workflows/e2e-api-tests.yaml` | | 14 | `.github/workflows/e2e-source-of-truth-api-tests.yaml` | | 15 | `.github/workflows/restart-dev-and-test.yaml` | | 16 | `apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts` | | 17 | `apps/e2e/tests/backend/endpoints/api/v1/internal/email.test.ts` | | 18 | `apps/e2e/tests/backend/endpoints/api/v1/send-email.test.ts` | | 19 | `apps/e2e/tests/backend/endpoints/api/v1/unsubscribe-link.test.ts` | | 20 | `apps/e2e/tests/backend/workflows.test.ts` | | 21 | `docs/templates/others/self-host.mdx` |
[![Need help? Join our Discord](https://img.shields.io/badge/Need%20help%3F%20Join%20our%20Discord-5865F2?style=plastic&logo=discord&logoColor=white)](https://discord.gg/n3SsVDAW6U) [![Analyze latest changes](https://img.shields.io/badge/Analyze%20latest%20changes-238636?style=plastic)](https://squash-322339097191.europe-west3.run.app/interactive/21c3d5c36237cfa8aa584be4a22457c7e4e393688dd800b78799b3ef730fd693/?repo_owner=stack-auth&repo_name=stack-auth&pr_number=962) ---- > [!IMPORTANT] > This PR introduces customizable development ports using `NEXT_PUBLIC_STACK_PORT_PREFIX`, updating configurations, documentation, and tests accordingly. > > - **Behavior**: > - Default development ports for services are now customizable via `NEXT_PUBLIC_STACK_PORT_PREFIX`. > - PostgreSQL port changed from `5432` to `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}28`. > - Inbucket SMTP port changed from `2500` to `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}29`. > - Inbucket POP3 port changed from `1100` to `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}30`. > - OpenTelemetry collector port changed from `4318` to `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}31`. > - **Configuration**: > - Updated `docker.compose.yaml` to use new port variables for services like PostgreSQL, Inbucket, and OpenTelemetry. > - Environment files in `apps/backend`, `apps/dashboard`, and `apps/e2e` updated to use `NEXT_PUBLIC_STACK_PORT_PREFIX`. > - `package.json` scripts updated to reflect new port configurations. > - **Documentation**: > - Added `CLAUDE-KNOWLEDGE.md` to document new port mappings. > - Updated `self-host.mdx` to reflect new port configurations. > - **Testing**: > - Updated test files in `apps/e2e/tests` to use new port configurations. > - Added `helpers/ports.ts` for port-related utilities in tests. > > This description was created by [Ellipsis](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral) for 76ef55f58f9e6a9a750d6485b5d62bc85ca6a67c. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed. ---- ## Summary by CodeRabbit - **New Features** - Enable configurable development ports via a NEXT_PUBLIC_STACK_PORT_PREFIX, allowing parallel local environments with custom port prefixes. - **Bug Fixes** - Updated local service port mappings and CI/workflow settings so tooling and tests use the new prefixed ports consistently. - **Documentation** - Added docs and contributor guidance for running multiple parallel workspaces with custom port prefixes. --------- Co-authored-by: N2D4 --- .claude/settings.json | 2 +- .devcontainer/devcontainer.json | 4 - .../workflows/check-prisma-migrations.yaml | 4 +- .github/workflows/docker-emulator-test.yaml | 11 +- .github/workflows/docker-server-test.yaml | 2 +- .github/workflows/e2e-api-tests.yaml | 4 +- .../e2e-custom-base-port-api-tests.yaml | 150 ++++++++++++++++++ .../e2e-source-of-truth-api-tests.yaml | 10 +- ...rt-dev-and-test-with-custom-base-port.yaml | 47 ++++++ .github/workflows/restart-dev-and-test.yaml | 2 - .gitignore | 1 + .vscode/extensions.json | 1 - AGENTS.md | 6 +- CONTRIBUTING.md | 42 +++++ apps/backend/.env | 9 +- apps/backend/.env.development | 20 +-- apps/backend/package.json | 6 +- apps/backend/src/instrumentation.ts | 2 +- apps/backend/src/lib/freestyle.tsx | 3 +- apps/backend/src/lib/stripe.tsx | 3 +- apps/backend/src/polyfills.tsx | 18 ++- apps/backend/src/prisma-client.tsx | 4 + apps/backend/vitest.config.ts | 1 + apps/backend/vitest.setup.ts | 1 + apps/dashboard/.env.development | 4 +- apps/dashboard/package.json | 4 +- apps/dashboard/src/instrumentation.ts | 2 +- apps/dashboard/src/lib/env.tsx | 39 +++-- apps/dashboard/src/polyfills.tsx | 16 ++ apps/dev-launchpad/.gitignore | 1 + apps/dev-launchpad/package.json | 2 +- apps/dev-launchpad/public/index.html | 108 ++++++------- .../dev-launchpad/scripts/write-env-config.js | 11 ++ apps/e2e/.env | 2 +- apps/e2e/.env.development | 10 +- apps/e2e/tests/backend/backend-helpers.ts | 11 +- .../endpoints/api/migration-tests.test.ts | 3 +- .../api/v1/auth/oauth/authorize.test.ts | 13 +- .../v1/auth/oauth/wildcard-domains.test.ts | 5 +- .../api/v1/connected-accounts.test.ts | 17 +- .../api/v1/integrations/custom/oauth.test.ts | 18 +-- .../custom/projects/transfer.test.ts | 2 +- .../api/v1/integrations/neon/oauth.test.ts | 18 +-- .../neon/projects/current.test.ts | 2 +- .../neon/projects/transfer.test.ts | 2 +- .../api/v1/internal/email-drafts.test.ts | 5 +- .../endpoints/api/v1/internal/email.test.ts | 8 +- .../api/v1/internal/projects.test.ts | 2 +- .../outdated--create-purchase-url.test.ts | 5 +- .../outdated--purchase-session.test.ts | 3 +- .../v1/payments/create-purchase-url.test.ts | 5 +- .../api/v1/payments/purchase-session.test.ts | 3 +- .../backend/endpoints/api/v1/projects.test.ts | 6 +- .../endpoints/api/v1/send-email.test.ts | 12 +- .../backend/endpoints/api/v1/teams.test.ts | 4 +- .../endpoints/api/v1/unsubscribe-link.test.ts | 10 +- .../backend/endpoints/api/v1/users.test.ts | 9 +- apps/e2e/tests/backend/workflows.test.ts | 3 +- apps/e2e/tests/helpers.ts | 44 ++--- apps/e2e/tests/helpers/ports.ts | 12 ++ apps/e2e/tests/js/payments.test.ts | 3 +- apps/e2e/tests/snapshot-serializer.ts | 7 +- apps/mock-oauth-server/src/index.ts | 10 +- claude/CLAUDE-KNOWLEDGE.md | 4 + docker/dependencies/docker.compose.yaml | 39 +++-- docker/emulator/docker.compose.yaml | 7 +- docker/server/.env.example | 4 +- docker/server/entrypoint.sh | 8 +- docs/.env.development | 2 +- docs/package.json | 4 +- docs/templates/others/self-host.mdx | 2 +- examples/cjs-test/.env.development | 2 +- examples/cjs-test/package.json | 4 +- examples/convex/.env.development | 2 +- examples/convex/package.json | 6 +- examples/convex/stack/client.tsx | 1 - examples/demo/.env.development | 4 +- examples/demo/package.json | 4 +- examples/docs-examples/.env.development | 2 +- examples/docs-examples/package.json | 4 +- examples/e-commerce/.env.development | 2 +- examples/e-commerce/package.json | 4 +- examples/js-example/.env.development | 4 +- examples/js-example/package.json | 2 +- examples/middleware/.env.development | 2 +- examples/middleware/package.json | 4 +- examples/react-example/.env.development | 2 +- examples/react-example/package.json | 2 +- examples/supabase/.env.development | 2 +- examples/supabase/package.json | 4 +- package.json | 6 +- .../src/utils/strings.nicify.test.ts | 8 + packages/stack-shared/src/utils/strings.tsx | 33 ++-- .../stack-app/apps/implementations/common.ts | 8 +- 94 files changed, 676 insertions(+), 300 deletions(-) create mode 100644 .github/workflows/e2e-custom-base-port-api-tests.yaml create mode 100644 .github/workflows/restart-dev-and-test-with-custom-base-port.yaml create mode 100644 apps/backend/vitest.setup.ts create mode 100644 apps/dev-launchpad/.gitignore create mode 100644 apps/dev-launchpad/scripts/write-env-config.js create mode 100644 apps/e2e/tests/helpers/ports.ts create mode 100644 claude/CLAUDE-KNOWLEDGE.md diff --git a/.claude/settings.json b/.claude/settings.json index ebf929413..e7eb8e218 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -21,7 +21,7 @@ "hooks": [ { "type": "command", - "command": "if ! curl -s --connect-timeout 1 http://localhost:8102 >/dev/null 2>&1; then echo -e '\\n\\n\\033[1;31mCannot reach backend on port 8102! Please run `pnpm run dev` before querying Claude Code\\033[0m\\n\\n' >&2; exit 2; fi" + "command": "PORT=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02; if ! curl -s --connect-timeout 1 \"http://localhost:$PORT\" >/dev/null 2>&1; then echo -e \"\\n\\n\\033[1;31mCannot reach backend on port $PORT! Please run \\`pnpm run dev\\` before querying Claude Code\\033[0m\\n\\n\" >&2; exit 2; fi" } ] } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ae2b49b5c..9819dad73 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,10 +14,6 @@ "memory": "16gb" }, "forwardPorts": [ - 5432, // PostgreSQL - 2500, // Inbucket SMTP - 1100, // Inbucket POP3 - 4318, // OTel collector 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8109, 8110, 8111, 8112, 8113, 8114, 8115, 8116, 8117, 8118, 8119, 8120, 8121, 8122, 8123, 8124, 8125, 8126, 8127, 8128, 8129, diff --git a/.github/workflows/check-prisma-migrations.yaml b/.github/workflows/check-prisma-migrations.yaml index 1442854db..a1d6ed9f1 100644 --- a/.github/workflows/check-prisma-migrations.yaml +++ b/.github/workflows/check-prisma-migrations.yaml @@ -62,9 +62,9 @@ jobs: - name: Remove auto-migration metadata run: | cd apps/backend - pnpm run prisma db execute --url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe --stdin <<'SQL' + pnpm run prisma db execute --url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe --stdin <<'SQL' DROP TABLE IF EXISTS "SchemaMigration"; SQL - name: Check for differences in Prisma schema and current DB - run: cd apps/backend && pnpm run prisma migrate diff --from-url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe --to-schema-datamodel ./prisma/schema.prisma --shadow-database-url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/shadow_db --exit-code + run: cd apps/backend && pnpm run prisma migrate diff --from-url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe --to-schema-datamodel ./prisma/schema.prisma --shadow-database-url postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/shadow_db --exit-code diff --git a/.github/workflows/docker-emulator-test.yaml b/.github/workflows/docker-emulator-test.yaml index 0cab2d62c..892e4d3d6 100644 --- a/.github/workflows/docker-emulator-test.yaml +++ b/.github/workflows/docker-emulator-test.yaml @@ -1,11 +1,12 @@ name: Docker Emulator Test on: - push: - branches: - - main - - dev - pull_request: + workflow_dispatch # skip this workflow for now, unless manually triggered +# push: +# branches: +# - main +# - dev +# pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/docker-server-test.yaml b/.github/workflows/docker-server-test.yaml index b5ab61d24..66f6ad499 100644 --- a/.github/workflows/docker-server-test.yaml +++ b/.github/workflows/docker-server-test.yaml @@ -19,7 +19,7 @@ jobs: - name: Setup postgres run: | - docker run -d --name db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=stackframe -p 5432:5432 postgres:latest + docker run -d --name db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=stackframe -p 8128:5432 postgres:latest sleep 5 docker logs db diff --git a/.github/workflows/e2e-api-tests.yaml b/.github/workflows/e2e-api-tests.yaml index 8a17b12c7..d3d15a987 100644 --- a/.github/workflows/e2e-api-tests.yaml +++ b/.github/workflows/e2e-api-tests.yaml @@ -17,7 +17,7 @@ jobs: env: NODE_ENV: test STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes - STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe" + STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe" strategy: matrix: @@ -91,7 +91,7 @@ jobs: run: pnpm run wait-until-postgres-is-ready:pg_isready - name: Wait on Inbucket - run: npx wait-on tcp:localhost:2500 + run: npx wait-on tcp:localhost:8129 - name: Wait on Svix run: npx wait-on tcp:localhost:8113 diff --git a/.github/workflows/e2e-custom-base-port-api-tests.yaml b/.github/workflows/e2e-custom-base-port-api-tests.yaml new file mode 100644 index 000000000..e2853b367 --- /dev/null +++ b/.github/workflows/e2e-custom-base-port-api-tests.yaml @@ -0,0 +1,150 @@ +name: Runs E2E API Tests with custom port prefix + +on: + push: + branches: + - main + - dev + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }} + +jobs: + build: + runs-on: ubicloud-standard-8 + env: + NODE_ENV: test + STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes + STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:6728/stackframe" + NEXT_PUBLIC_STACK_PORT_PREFIX: "67" + + strategy: + matrix: + node-version: [22.x] + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9.1.2 + + # Even just starting the Docker Compose as a daemon is slow because we have to download and build the images + # so, we run it in the background + - name: Start Docker Compose in background + uses: JarvusInnovations/background-action@v1.0.7 + with: + run: docker compose -f docker/dependencies/docker.compose.yaml up -d & + # we don't need to wait on anything, just need to start the daemon + wait-on: /dev/null + tail: true + wait-for: 3s + log-output-if: true + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Create .env.test.local file for apps/backend + run: cp apps/backend/.env.development apps/backend/.env.test.local + + - name: Create .env.test.local file for apps/dashboard + run: cp apps/dashboard/.env.development apps/dashboard/.env.test.local + + - name: Create .env.test.local file for apps/e2e + run: cp apps/e2e/.env.development apps/e2e/.env.test.local + + - name: Create .env.test.local file for docs + run: cp docs/.env.development docs/.env.test.local + + - name: Create .env.test.local file for examples/cjs-test + run: cp examples/cjs-test/.env.development examples/cjs-test/.env.test.local + + - name: Create .env.test.local file for examples/demo + run: cp examples/demo/.env.development examples/demo/.env.test.local + + - name: Create .env.test.local file for examples/docs-examples + run: cp examples/docs-examples/.env.development examples/docs-examples/.env.test.local + + - name: Create .env.test.local file for examples/e-commerce + run: cp examples/e-commerce/.env.development examples/e-commerce/.env.test.local + + - name: Create .env.test.local file for examples/middleware + run: cp examples/middleware/.env.development examples/middleware/.env.test.local + + - name: Create .env.test.local file for examples/supabase + run: cp examples/supabase/.env.development examples/supabase/.env.test.local + + - name: Create .env.test.local file for examples/convex + run: cp examples/convex/.env.development examples/convex/.env.test.local + + - name: Build + run: pnpm build + + - name: Wait on Postgres + run: pnpm run wait-until-postgres-is-ready:pg_isready + + - name: Wait on Inbucket + run: npx wait-on tcp:localhost:6729 + + - name: Wait on Svix + run: npx wait-on tcp:localhost:6713 + + - name: Initialize database + run: pnpm run db:init + + - name: Start stack-backend in background + uses: JarvusInnovations/background-action@v1.0.7 + with: + run: pnpm run start:backend --log-order=stream & + wait-on: | + http://localhost:6702 + tail: true + wait-for: 30s + log-output-if: true + - name: Start stack-dashboard in background + uses: JarvusInnovations/background-action@v1.0.7 + with: + run: pnpm run start:dashboard --log-order=stream & + wait-on: | + http://localhost:6702 + tail: true + wait-for: 30s + log-output-if: true + - name: Start mock-oauth-server in background + uses: JarvusInnovations/background-action@v1.0.7 + with: + run: pnpm run start:mock-oauth-server --log-order=stream & + wait-on: | + http://localhost:6702 + tail: true + wait-for: 30s + log-output-if: true + + - name: Wait 10 seconds + run: sleep 10 + + - name: Run tests + run: pnpm test + + - name: Run tests again, to make sure they are stable (attempt 1) + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' + run: pnpm test + + - name: Run tests again, to make sure they are stable (attempt 2) + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' + run: pnpm test + + - name: Verify data integrity + run: pnpm run verify-data-integrity + + - name: Print Docker Compose logs + if: always() + run: docker compose -f docker/dependencies/docker.compose.yaml logs diff --git a/.github/workflows/e2e-source-of-truth-api-tests.yaml b/.github/workflows/e2e-source-of-truth-api-tests.yaml index 005d92267..4c8811e5f 100644 --- a/.github/workflows/e2e-source-of-truth-api-tests.yaml +++ b/.github/workflows/e2e-source-of-truth-api-tests.yaml @@ -17,9 +17,9 @@ jobs: env: NODE_ENV: test STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes - STACK_OVERRIDE_SOURCE_OF_TRUTH: '{"type": "postgres", "connectionString": "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/source-of-truth-db?schema=sot-schema"}' + STACK_OVERRIDE_SOURCE_OF_TRUTH: '{"type": "postgres", "connectionString": "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/source-of-truth-db?schema=sot-schema"}' STACK_TEST_SOURCE_OF_TRUTH: true - STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe" + STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe" strategy: matrix: @@ -93,15 +93,15 @@ jobs: run: pnpm run wait-until-postgres-is-ready:pg_isready - name: Wait on Inbucket - run: npx wait-on tcp:localhost:2500 + run: npx wait-on tcp:localhost:8129 - name: Wait on Svix run: npx wait-on tcp:localhost:8113 - name: Create source-of-truth database and schema run: | - psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/postgres -c "CREATE DATABASE \"source-of-truth-db\";" - psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/source-of-truth-db -c "CREATE SCHEMA \"sot-schema\";" + psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/postgres -c "CREATE DATABASE \"source-of-truth-db\";" + psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/source-of-truth-db -c "CREATE SCHEMA \"sot-schema\";" - name: Initialize database run: pnpm run db:init diff --git a/.github/workflows/restart-dev-and-test-with-custom-base-port.yaml b/.github/workflows/restart-dev-and-test-with-custom-base-port.yaml new file mode 100644 index 000000000..971d90190 --- /dev/null +++ b/.github/workflows/restart-dev-and-test-with-custom-base-port.yaml @@ -0,0 +1,47 @@ +name: "Dev Environment Test With Custom Base Port" + +on: + push: + branches: + - main + - dev + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }} + +env: + SHELL: /usr/bin/bash + +jobs: + restart-dev-and-test-with-custom-base-port: + runs-on: ubicloud-standard-8 + env: + NEXT_PUBLIC_STACK_PORT_PREFIX: "69" + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js v20 + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9.1.2 + + - name: Install packages + run: pnpm install + + - name: Start dev environment + run: pnpm run restart-dev-environment + + - name: Run tests + run: pnpm run test --reporter=verbose + + - name: Print dev server logs + run: cat dev-server.log.untracked.txt + if: always() diff --git a/.github/workflows/restart-dev-and-test.yaml b/.github/workflows/restart-dev-and-test.yaml index 50bda5f82..0932f138d 100644 --- a/.github/workflows/restart-dev-and-test.yaml +++ b/.github/workflows/restart-dev-and-test.yaml @@ -17,8 +17,6 @@ env: jobs: restart-dev-and-test: runs-on: ubicloud-standard-8 - env: - STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe" steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 991eda063..ce1a7c8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.untracked *.untracked.* node-compile-cache/ +.envrc *.cpuprofile diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e9c48cf96..fcf818088 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -9,7 +9,6 @@ "YoavBls.pretty-ts-errors", "mxsdev.typescript-explorer", "github.vscode-github-actions", - "fabiospampinato.vscode-highlight", "Prisma.prisma" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. diff --git a/AGENTS.md b/AGENTS.md index 54be6f1d5..902ab8827 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,11 +34,11 @@ You should ALWAYS add new E2E tests when you change the API or SDK interface. Ge Stack Auth is a monorepo using Turbo for build orchestration. The main components are: ### Apps (`/apps`) -- **backend** (`/apps/backend`): Next.js API backend running on port 8102 +- **backend** (`/apps/backend`): Next.js API backend running on port `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02` (defaults to 8102) - Main API routes in `/apps/backend/src/app/api/latest` - Database models using Prisma -- **dashboard** (`/apps/dashboard`): Admin dashboard on port 8101 -- **dev-launchpad**: Development portal on port 8100 +- **dashboard** (`/apps/dashboard`): Admin dashboard on port `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01` (defaults to 8101) +- **dev-launchpad**: Development portal on port `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}00` (defaults to 8100) - **e2e**: End-to-end tests ### Packages (`/packages`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e361f4901..29bba0fee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,7 @@ Due to the nature of authentication, this may not be the easiest project to cont - [How to contribute](#how-to-contribute) - [Security & bug bounties](#security--bug-bounties) +- [Vibecoding setup](#vibecoding-setup) - [Before creating a pull request](#before-creating-a-pull-request) @@ -31,6 +32,47 @@ If you think Stack Auth is a good fit for you, follow these steps: For any security-related concerns & bug bounties, please email us at [security@stack-auth.com](mailto:security@stack-auth.com). +## Vibecoding setup + +NOTE: Every line of code should be reviewed by a human BEFORE you submit a PR. DO NOT waste our time by creating an AI-generated PR; we won't fall for that. + +For vibecoding, it can help to have multiple parallel copies of the codebase open in different windows. For this, you can set the environment variable `NEXT_PUBLIC_STACK_PORT_PREFIX` to a different value (default 81). You may want to do this in your VSCode settings: + +```json +{ + "terminal.integrated.env.osx": { + "NEXT_PUBLIC_STACK_PORT_PREFIX": "181" + }, + "terminal.integrated.env.linux": { + "NEXT_PUBLIC_STACK_PORT_PREFIX": "181" + }, + "terminal.integrated.env.windows": { + "NEXT_PUBLIC_STACK_PORT_PREFIX": "181" + }, + "claude-code.environmentVariables": [ + "NEXT_PUBLIC_STACK_PORT_PREFIX=181" + ] +} +``` + +This will work for Claude Code and everything that runs inside the integrated terminal, but it does not work with Cursor Agent or the Codex VSCode extension. To make it work for these, the recommendation is that instead you use `direnv` with a `.envrc` file: + +```sh +# .envrc +# make sure to install direnv and add it to your shell rc file (e.g. ~/.bashrc or ~/.zshrc) +export NEXT_PUBLIC_STACK_PORT_PREFIX=181 +``` + +And make sure that `direnv` works in non-interactive login shells, make sure you added it to your ~/.bash_profile as well as your ~/.bashrc: + +```sh +# ~/.bash_profile, ~/.bashrc, ~/.zprofile, ~/.zshrc, ~/.zshenv, etc. +# note that different coding agents use a different shell in a different mode (login, non-login, interactive, non-interactive, etc.); from my experimentation, as of 2025-10-17 on a Mac, Cursor uses non-interactive zsh (requiring ~/.zshenv), whereas Codex uses a non-interactive login bash (requiring ~/.bash_profile). It's easiest to just add these lines of code to all of your shell configs. +eval "$(direnv hook )" +eval "$(direnv export )" +``` + +When you do this, it is recommended that you give all workspaces a port prefix other than 81, to prevent accidental conflicts when you forgot to make a feature support the $NEXT_PUBLIC_STACK_PORT_PREFIX environment variable. (for example: first workspace at 181, second workspace at 182, etc.) ## Before creating a pull request diff --git a/apps/backend/.env b/apps/backend/.env index 5c2d486e5..66c370708 100644 --- a/apps/backend/.env +++ b/apps/backend/.env @@ -36,15 +36,15 @@ STACK_ALLOW_SHARED_OAUTH_ACCESS_TOKENS=# allow shared oauth provider to also use # Email # For local development, you can spin up a local SMTP server like inbucket STACK_EMAIL_HOST=# for local inbucket: 127.0.0.1 -STACK_EMAIL_PORT=# for local inbucket: 2500 +STACK_EMAIL_PORT=# for local inbucket: 8129 STACK_EMAIL_USERNAME=# for local inbucket: test STACK_EMAIL_PASSWORD=# for local inbucket: none STACK_EMAIL_SENDER=# for local inbucket: noreply@test.com STACK_EMAILABLE_API_KEY=# for Emailable email validation, see https://emailable.com # Database -# For local development: `docker run -it --rm -e POSTGRES_PASSWORD=password -p "5432:5432" postgres` -STACK_DATABASE_CONNECTION_STRING=# enter your connection string here. For local development: `postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe` +# For local development: `docker run -it --rm -e POSTGRES_PASSWORD=password -p "8128:5432" postgres` +STACK_DATABASE_CONNECTION_STRING=# enter your connection string here. For local development: `postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe` STACK_DIRECT_DATABASE_CONNECTION_STRING=# enter your direct (unpooled or session mode) database connection string here. For local development: same as above # Webhooks @@ -76,11 +76,10 @@ STACK_QSTASH_NEXT_SIGNING_KEY= # Misc, optional STACK_ACCESS_TOKEN_EXPIRATION_TIME=# enter the expiration time for the access token here. Optional, don't specify it for default value STACK_SETUP_ADMIN_GITHUB_ID=# enter the account ID of the admin user here, and after running the seed script they will be able to access the internal project in the Stack dashboard. Optional, don't specify it for default value -OTEL_EXPORTER_OTLP_ENDPOINT=# enter the OpenTelemetry endpoint here. Optional, default is `http://localhost:4318` +OTEL_EXPORTER_OTLP_ENDPOINT=# enter the OpenTelemetry endpoint here. Optional, default is `http://localhost:8131` STACK_INTEGRATION_CLIENTS_CONFIG=# a list of oidc-provider clients for integrations. If not provided, disables integrations STACK_FREESTYLE_API_KEY=# enter your freestyle.sh api key STACK_OPENAI_API_KEY=# enter your openai api key STACK_FEATUREBASE_API_KEY=# enter your featurebase api key STACK_STRIPE_SECRET_KEY=# enter your stripe api key STACK_STRIPE_WEBHOOK_SECRET=# enter your stripe webhook secret - diff --git a/apps/backend/.env.development b/apps/backend/.env.development index 42fd65b02..ccaa92f7e 100644 --- a/apps/backend/.env.development +++ b/apps/backend/.env.development @@ -1,5 +1,5 @@ -NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 -NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101 +NEXT_PUBLIC_STACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02 +NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01 STACK_SERVER_SECRET=23-wuNpik0gIW4mruTz25rbIvhuuvZFrLOLtL7J4tyo STACK_SEED_INTERNAL_PROJECT_SIGN_UP_ENABLED=true @@ -11,7 +11,7 @@ STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-i STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=this-super-secret-admin-key-is-for-local-development-only -STACK_OAUTH_MOCK_URL=http://localhost:8114 +STACK_OAUTH_MOCK_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}14 STACK_GITHUB_CLIENT_ID=MOCK STACK_GITHUB_CLIENT_SECRET=MOCK @@ -24,11 +24,11 @@ STACK_SPOTIFY_CLIENT_SECRET=MOCK STACK_ALLOW_SHARED_OAUTH_ACCESS_TOKENS=true -STACK_DATABASE_CONNECTION_STRING=postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe?connection_limit=20 -STACK_DIRECT_DATABASE_CONNECTION_STRING=postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe?connection_limit=20 +STACK_DATABASE_CONNECTION_STRING=postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}28/stackframe?connection_limit=20 +STACK_DIRECT_DATABASE_CONNECTION_STRING=postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}28/stackframe?connection_limit=20 STACK_EMAIL_HOST=127.0.0.1 -STACK_EMAIL_PORT=2500 +STACK_EMAIL_PORT=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}29 STACK_EMAIL_SECURE=false STACK_EMAIL_USERNAME=does not matter, ignored by Inbucket STACK_EMAIL_PASSWORD=does not matter, ignored by Inbucket @@ -36,7 +36,7 @@ STACK_EMAIL_SENDER=noreply@example.com STACK_ACCESS_TOKEN_EXPIRATION_TIME=30s -STACK_SVIX_SERVER_URL=http://localhost:8113 +STACK_SVIX_SERVER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}13 STACK_SVIX_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTUxNDA2MzksImV4cCI6MTk3MDUwMDYzOSwibmJmIjoxNjU1MTQwNjM5LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.En8w77ZJWbd0qrMlHHupHUB-4cx17RfzFykseg95SUk STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS=500 @@ -51,7 +51,7 @@ STACK_STRIPE_SECRET_KEY=sk_test_mockstripekey STACK_STRIPE_WEBHOOK_SECRET=mock_stripe_webhook_secret # S3 Configuration for local development using s3mock -STACK_S3_ENDPOINT=http://localhost:8121 +STACK_S3_ENDPOINT=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}21 STACK_S3_REGION=us-east-1 STACK_S3_ACCESS_KEY_ID=s3mockroot STACK_S3_SECRET_ACCESS_KEY=s3mockroot @@ -59,12 +59,12 @@ STACK_S3_BUCKET=stack-storage # AWS region defaults to LocalStack STACK_AWS_REGION=us-east-1 -STACK_AWS_KMS_ENDPOINT=http://localhost:8124 +STACK_AWS_KMS_ENDPOINT=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}24 STACK_AWS_ACCESS_KEY_ID=test STACK_AWS_SECRET_ACCESS_KEY=test # Upstash defaults to one of the pre-build test users of the local emulator -STACK_QSTASH_URL=http://localhost:8125 +STACK_QSTASH_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}25 STACK_QSTASH_TOKEN=eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0= STACK_QSTASH_CURRENT_SIGNING_KEY=sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r STACK_QSTASH_NEXT_SIGNING_KEY=sig_5ZB6DVzB1wjE8S6rZ7eenA8Pdnhs diff --git a/apps/backend/package.json b/apps/backend/package.json index ab61cf16c..1976c8a61 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -7,12 +7,12 @@ "typecheck": "tsc --noEmit", "with-env": "dotenv -c development --", "with-env:prod": "dotenv -c --", - "dev": "concurrently -n \"dev,codegen,prisma-studio\" -k \"next dev --turbopack --port 8102\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\"", + "dev": "concurrently -n \"dev,codegen,prisma-studio\" -k \"next dev --turbopack --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\"", "build": "pnpm run codegen && next build", "docker-build": "pnpm run codegen && next build --experimental-build-mode compile", "build-self-host-migration-script": "tsup --config scripts/db-migrations.tsup.config.ts", "analyze-bundle": "ANALYZE_BUNDLE=1 pnpm run build", - "start": "next start --port 8102", + "start": "next start --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02", "codegen-prisma": "pnpm run prisma generate", "codegen-prisma:watch": "pnpm run prisma generate --watch", "codegen-route-info": "pnpm run with-env tsx scripts/generate-route-info.ts", @@ -21,7 +21,7 @@ "codegen:watch": "concurrently -n \"prisma,docs,route-info,migration-imports\" -k \"pnpm run codegen-prisma:watch\" \"pnpm run watch-docs\" \"pnpm run codegen-route-info:watch\" \"pnpm run generate-migration-imports:watch\"", "psql-inner": "psql $(echo $STACK_DATABASE_CONNECTION_STRING | sed 's/\\?.*$//')", "psql": "pnpm run with-env pnpm run psql-inner", - "prisma-studio": "pnpm run with-env prisma studio --port 8106 --browser none", + "prisma-studio": "pnpm run with-env prisma studio --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}06 --browser none", "prisma": "pnpm run with-env prisma", "db:migration-gen": "pnpm run with-env tsx scripts/db-migrations.ts generate-migration-file", "db:reset": "pnpm run with-env tsx scripts/db-migrations.ts reset", diff --git a/apps/backend/src/instrumentation.ts b/apps/backend/src/instrumentation.ts index a44f89b6b..2140f85bb 100644 --- a/apps/backend/src/instrumentation.ts +++ b/apps/backend/src/instrumentation.ts @@ -17,7 +17,7 @@ export function register() { }); if (getNextRuntime() === "nodejs") { - process.title = "stack-backend (node/nextjs)"; + process.title = `stack-backend:${getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81")} (node/nextjs)`; } if (getNextRuntime() === "nodejs" || getNextRuntime() === "edge") { diff --git a/apps/backend/src/lib/freestyle.tsx b/apps/backend/src/lib/freestyle.tsx index 150d3e9b6..cb6037052 100644 --- a/apps/backend/src/lib/freestyle.tsx +++ b/apps/backend/src/lib/freestyle.tsx @@ -15,7 +15,8 @@ export class Freestyle { if (!["development", "test"].includes(getNodeEnvironment())) { throw new StackAssertionError("Mock Freestyle key used in production; please set the STACK_FREESTYLE_API_KEY environment variable."); } - baseUrl = "http://localhost:8122"; + const prefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); + baseUrl = `http://localhost:${prefix}22`; } this.freestyle = new FreestyleSandboxes({ apiKey, diff --git a/apps/backend/src/lib/stripe.tsx b/apps/backend/src/lib/stripe.tsx index 3ab57d284..d7326c4fe 100644 --- a/apps/backend/src/lib/stripe.tsx +++ b/apps/backend/src/lib/stripe.tsx @@ -9,10 +9,11 @@ import { createStripeProxy, type StripeOverridesMap } from "./stripe-proxy"; const stripeSecretKey = getEnvVariable("STACK_STRIPE_SECRET_KEY"); const useStripeMock = stripeSecretKey === "sk_test_mockstripekey" && ["development", "test"].includes(getNodeEnvironment()); +const stackPortPrefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); const stripeConfig: Stripe.StripeConfig = useStripeMock ? { protocol: "http", host: "localhost", - port: 8123, + port: Number(`${stackPortPrefix}23`), } : {}; export const getStackStripe = (overrides?: StripeOverridesMap) => { diff --git a/apps/backend/src/polyfills.tsx b/apps/backend/src/polyfills.tsx index 3ff2a398b..ca2dc5c52 100644 --- a/apps/backend/src/polyfills.tsx +++ b/apps/backend/src/polyfills.tsx @@ -1,13 +1,29 @@ import * as Sentry from "@sentry/nextjs"; -import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; +import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; import { captureError, registerErrorSink } from "@stackframe/stack-shared/dist/utils/errors"; import * as util from "util"; +function expandStackPortPrefix(value?: string | null) { + if (!value) return value ?? undefined; + const prefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); + return prefix ? value.replace(/\$\{NEXT_PUBLIC_STACK_PORT_PREFIX:-81\}/g, prefix) : value; +} + const sentryErrorSink = (location: string, error: unknown) => { Sentry.captureException(error, { extra: { location } }); }; export function ensurePolyfilled() { + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith("STACK_") || key.startsWith("NEXT_PUBLIC_STACK_")) { + const replaced = expandStackPortPrefix(value ?? undefined); + if (replaced !== undefined) { + // eslint-disable-next-line no-restricted-syntax + process.env[key] = replaced; + } + } + } + registerErrorSink(sentryErrorSink); if ("addEventListener" in globalThis) { diff --git a/apps/backend/src/prisma-client.tsx b/apps/backend/src/prisma-client.tsx index 9b5f8aa29..9638e367f 100644 --- a/apps/backend/src/prisma-client.tsx +++ b/apps/backend/src/prisma-client.tsx @@ -14,6 +14,10 @@ import { isUuid } from "@stackframe/stack-shared/dist/utils/uuids"; import { isPromise } from "util/types"; import { runMigrationNeeded } from "./auto-migrations"; import { Tenancy } from "./lib/tenancies"; +import { ensurePolyfilled } from "./polyfills"; + +// just ensure we're polyfilled because this file relies on envvars being expanded +ensurePolyfilled(); export type PrismaClientTransaction = PrismaClient | Parameters[0]>[0]; diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts index 81122e248..c4b64b9f4 100644 --- a/apps/backend/vitest.config.ts +++ b/apps/backend/vitest.config.ts @@ -12,6 +12,7 @@ export default mergeConfig( ...loadEnv('', process.cwd(), ''), ...loadEnv('development', process.cwd(), ''), }, + setupFiles: ['./vitest.setup.ts'], }, resolve: { alias: { diff --git a/apps/backend/vitest.setup.ts b/apps/backend/vitest.setup.ts new file mode 100644 index 000000000..8b0a25d33 --- /dev/null +++ b/apps/backend/vitest.setup.ts @@ -0,0 +1 @@ +import "./src/polyfills"; diff --git a/apps/dashboard/.env.development b/apps/dashboard/.env.development index 9cb84bc67..3bb329db7 100644 --- a/apps/dashboard/.env.development +++ b/apps/dashboard/.env.development @@ -1,10 +1,10 @@ -NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only -NEXT_PUBLIC_STACK_SVIX_SERVER_URL=http://localhost:8113 +NEXT_PUBLIC_STACK_SVIX_SERVER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}13 STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS=50 NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR=true diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index d378d9c9c..f9e04c10c 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -7,11 +7,11 @@ "typecheck": "tsc --noEmit", "with-env": "dotenv -c development --", "with-env:prod": "dotenv -c --", - "dev": "next dev --turbopack --port 8101", + "dev": "next dev --turbopack --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01", "build": "next build", "docker-build": "next build --experimental-build-mode compile", "analyze-bundle": "ANALYZE_BUNDLE=1 pnpm run build", - "start": "next start --port 8101", + "start": "next start --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01", "psql": "pnpm run with-env bash -c 'psql $STACK_DATABASE_CONNECTION_STRING'", "lint": "next lint" }, diff --git a/apps/dashboard/src/instrumentation.ts b/apps/dashboard/src/instrumentation.ts index 50a4aad9f..faf6c8021 100644 --- a/apps/dashboard/src/instrumentation.ts +++ b/apps/dashboard/src/instrumentation.ts @@ -6,7 +6,7 @@ import "./polyfills"; export function register() { if (getNextRuntime() === "nodejs") { - globalThis.process.title = "stack-dashboard (node/nextjs)"; + globalThis.process.title = `stack-dashboard:${getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81")} (node/nextjs)`; } if (getNextRuntime() === "nodejs" || getNextRuntime() === "edge") { diff --git a/apps/dashboard/src/lib/env.tsx b/apps/dashboard/src/lib/env.tsx index f569c3f7f..c0b6ad361 100644 --- a/apps/dashboard/src/lib/env.tsx +++ b/apps/dashboard/src/lib/env.tsx @@ -1,16 +1,16 @@ /* eslint-disable no-restricted-syntax */ + +export function expandStackPortPrefix(value?: string | null) { + if (!value) return value ?? undefined; + const prefix = getPublicEnvVar("NEXT_PUBLIC_STACK_PORT_PREFIX") ?? "81"; + return prefix ? value.replace(/\$\{NEXT_PUBLIC_STACK_PORT_PREFIX:-81\}/g, prefix as string) : value; +} + const _inlineEnvVars = { NEXT_PUBLIC_STACK_API_URL: process.env.NEXT_PUBLIC_STACK_API_URL, - NEXT_PUBLIC_BROWSER_STACK_API_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_API_URL, - NEXT_PUBLIC_SERVER_STACK_API_URL: process.env.NEXT_PUBLIC_SERVER_STACK_API_URL, NEXT_PUBLIC_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL, - NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL, - NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL, - NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_STACK_SVIX_SERVER_URL: process.env.NEXT_PUBLIC_STACK_SVIX_SERVER_URL, - NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, - NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: process.env.NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY, NEXT_PUBLIC_STACK_EMULATOR_ENABLED: process.env.NEXT_PUBLIC_STACK_EMULATOR_ENABLED, NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID, NEXT_PUBLIC_STACK_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_PROJECT_ID, @@ -18,7 +18,24 @@ const _inlineEnvVars = { NEXT_PUBLIC_STACK_URL: process.env.NEXT_PUBLIC_STACK_URL, NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: process.env.NEXT_PUBLIC_STACK_INBUCKET_WEB_URL, NEXT_PUBLIC_STACK_ENABLE_DEVELOPMENT_FEATURES_PROJECT_IDS: process.env.NEXT_PUBLIC_STACK_ENABLE_DEVELOPMENT_FEATURES_PROJECT_IDS, - NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY + NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY, + NEXT_PUBLIC_STACK_PORT_PREFIX: process.env.NEXT_PUBLIC_STACK_PORT_PREFIX, + + + // TODO: NEXT_PUBLIC_BROWSER_STACK_API_URL should be renamed to NEXT_PUBLIC_STACK_BROWSER_API_URL + NEXT_PUBLIC_BROWSER_STACK_API_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_API_URL, + // TODO: NEXT_PUBLIC_SERVER_STACK_API_URL should be renamed to NEXT_PUBLIC_STACK_SERVER_API_URL + NEXT_PUBLIC_SERVER_STACK_API_URL: process.env.NEXT_PUBLIC_SERVER_STACK_API_URL, + // TODO: NEXT_PUBLIC_SENTRY_DSN should be renamed to NEXT_PUBLIC_STACK_SENTRY_DSN + NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, + // TODO: NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY should be renamed to NEXT_PUBLIC_STACK_VERSION_ALERTER_SEVERE_ONLY + NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: process.env.NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY, + // TODO: NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL should be renamed to NEXT_PUBLIC_STACK_BROWSER_DASHBOARD_URL + NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL, + // TODO: NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL should be renamed to NEXT_PUBLIC_STACK_SERVER_DASHBOARD_URL + NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL, + // TODO: NEXT_PUBLIC_POSTHOG_KEY should be renamed to NEXT_PUBLIC_STACK_POSTHOG_KEY + NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, } as const; // This will be replaced with the actual env vars after a docker build @@ -41,12 +58,13 @@ const _postBuildEnvVars = { NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_INBUCKET_WEB_URL", NEXT_PUBLIC_STACK_ENABLE_DEVELOPMENT_FEATURES_PROJECT_IDS: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_ENABLE_DEVELOPMENT_FEATURES_PROJECT_IDS", NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY", + NEXT_PUBLIC_STACK_PORT_PREFIX: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_PORT_PREFIX", } satisfies typeof _inlineEnvVars; // If this is not replaced with "true", then we will not use inline env vars const _usePostBuildEnvVars = 'STACK_ENV_VAR_SENTINEL_USE_INLINE_ENV_VARS'; -export function getPublicEnvVar(name: keyof typeof _inlineEnvVars) { +export function getPublicEnvVar(name: keyof typeof _inlineEnvVars): string | undefined { // This is a hack to force the compiler not to do any smart optimizations const _ = _usePostBuildEnvVars.toString() + _inlineEnvVars.toString(); // Force runtime evaluation @@ -55,6 +73,5 @@ export function getPublicEnvVar(name: keyof typeof _inlineEnvVars) { if (_usePostBuildEnvVars.slice(0) === 'true' && value && value.startsWith('STACK_ENV_VAR_SENTINEL')) { return undefined; } - return value; + return name === 'NEXT_PUBLIC_STACK_PORT_PREFIX' || !name.startsWith('NEXT_PUBLIC_STACK_') ? value : expandStackPortPrefix(value); } - diff --git a/apps/dashboard/src/polyfills.tsx b/apps/dashboard/src/polyfills.tsx index 318e34f5e..63b7316ab 100644 --- a/apps/dashboard/src/polyfills.tsx +++ b/apps/dashboard/src/polyfills.tsx @@ -2,12 +2,28 @@ import * as Sentry from "@sentry/nextjs"; import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; import { captureError, registerErrorSink } from "@stackframe/stack-shared/dist/utils/errors"; import * as util from "util"; +import { getPublicEnvVar } from "./lib/env"; + +function expandStackPortPrefix(value?: string | null) { + if (!value) return value ?? undefined; + const prefix = getPublicEnvVar("NEXT_PUBLIC_STACK_PORT_PREFIX") ?? "81"; + return prefix ? value.replace(/\$\{NEXT_PUBLIC_STACK_PORT_PREFIX:-81\}/g, prefix as string) : value; +} const sentryErrorSink = (location: string, error: unknown) => { Sentry.captureException(error, { extra: { location } }); }; export function ensurePolyfilled() { + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith("STACK_") || key.startsWith("NEXT_PUBLIC_STACK_")) { + const replaced = expandStackPortPrefix(value ?? undefined); + if (replaced !== undefined) { + process.env[key] = replaced; + } + } + } + registerErrorSink(sentryErrorSink); if ("addEventListener" in globalThis) { diff --git a/apps/dev-launchpad/.gitignore b/apps/dev-launchpad/.gitignore new file mode 100644 index 000000000..676037b55 --- /dev/null +++ b/apps/dev-launchpad/.gitignore @@ -0,0 +1 @@ +public/env-config.js diff --git a/apps/dev-launchpad/package.json b/apps/dev-launchpad/package.json index b2ae670d9..46dcb0358 100644 --- a/apps/dev-launchpad/package.json +++ b/apps/dev-launchpad/package.json @@ -3,7 +3,7 @@ "version": "2.8.44", "private": true, "scripts": { - "dev": "serve -p 8100 -s public", + "dev": "node scripts/write-env-config.js && serve -p ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}00 -s public", "clean": "rimraf node_modules" }, "dependencies": { diff --git a/apps/dev-launchpad/public/index.html b/apps/dev-launchpad/public/index.html index ba16e0597..24f53f59f 100644 --- a/apps/dev-launchpad/public/index.html +++ b/apps/dev-launchpad/public/index.html @@ -2,6 +2,7 @@ Stack Auth Dev Launchpad +