diff --git a/.github/workflows/qemu-emulator-build.yaml b/.github/workflows/qemu-emulator-build.yaml index a5a3f187d..5df149746 100644 --- a/.github/workflows/qemu-emulator-build.yaml +++ b/.github/workflows/qemu-emulator-build.yaml @@ -22,6 +22,8 @@ concurrency: env: EMULATOR_IMAGE_NAME: stack-local-emulator + EMULATOR_IMAGE_DIR: ${{ github.workspace }}/docker/local-emulator/qemu/images + EMULATOR_RUN_DIR: ${{ github.workspace }}/docker/local-emulator/qemu/run jobs: build: diff --git a/apps/backend/package.json b/apps/backend/package.json index f06c4723b..d8198c74f 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/backend", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "private": true, "type": "module", @@ -99,7 +99,7 @@ "jiti": "^2.6.1", "jose": "^6.1.3", "json-diff": "^1.0.6", - "next": "16.2.2", + "next": "16.1.7", "nodemailer": "^6.9.10", "oidc-provider": "^8.5.1", "openid-client": "5.6.4", diff --git a/apps/backend/prisma/seed.ts b/apps/backend/prisma/seed.ts index 30e8aae0e..ff3715d3b 100644 --- a/apps/backend/prisma/seed.ts +++ b/apps/backend/prisma/seed.ts @@ -259,6 +259,45 @@ export async function seed() { console.log('Internal team created'); } + // Upsert the internal API key set before any flake-prone work (dummy-project + // seed, email/svix, clickhouse). The emulator CLI authenticates against the + // internal project using the pck stored here, so it must land before the rest + // of the seed even if something later fails. + const isLocalEmulator = process.env.NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR === 'true'; + const rawPck = process.env.STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY; + if (isLocalEmulator && !rawPck) { + // Emulator images build before a per-VM pck is available. Runtime boots set + // STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY from the VM-generated + // random value and re-run the seed, which upserts the internal key set then. + console.log('Skipping internal API key set (no pck provided; emulator mode).'); + } else { + const keySet = { + publishableClientKey: rawPck || throwErr('STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY is not set'), + secretServerKey: isLocalEmulator + ? (process.env.STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY ?? null) + : (process.env.STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY || throwErr('STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY is not set')), + superSecretAdminKey: isLocalEmulator + ? (process.env.STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY ?? null) + : (process.env.STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY || throwErr('STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY is not set')), + }; + + await globalPrismaClient.apiKeySet.upsert({ + where: { projectId_id: { projectId: 'internal', id: apiKeyId } }, + update: { + ...keySet, + }, + create: { + id: apiKeyId, + projectId: 'internal', + description: "Internal API key set", + expiresAt: new Date('2099-12-31T23:59:59Z'), + ...keySet, + } + }); + + console.log('Updated internal API key set'); + } + const shouldSeedDummyProject = process.env.STACK_SEED_ENABLE_DUMMY_PROJECT === 'true'; if (shouldSeedDummyProject) { await seedDummyProject({ @@ -268,28 +307,6 @@ export async function seed() { }); } - const keySet = { - publishableClientKey: process.env.STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY || throwErr('STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY is not set'), - secretServerKey: process.env.STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY || throwErr('STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY is not set'), - superSecretAdminKey: process.env.STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY || throwErr('STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY is not set'), - }; - - await globalPrismaClient.apiKeySet.upsert({ - where: { projectId_id: { projectId: 'internal', id: apiKeyId } }, - update: { - ...keySet, - }, - create: { - id: apiKeyId, - projectId: 'internal', - description: "Internal API key set", - expiresAt: new Date('2099-12-31T23:59:59Z'), - ...keySet, - } - }); - - console.log('Updated internal API key set'); - // Create optional default admin user if credentials are provided. // This user will be able to login to the dashboard with both email/password and magic link. diff --git a/apps/backend/src/app/api/latest/internal/external-db-sync/poller/route.ts b/apps/backend/src/app/api/latest/internal/external-db-sync/poller/route.ts index d37ef118d..688b9d25c 100644 --- a/apps/backend/src/app/api/latest/internal/external-db-sync/poller/route.ts +++ b/apps/backend/src/app/api/latest/internal/external-db-sync/poller/route.ts @@ -130,7 +130,7 @@ export const GET = createSmartRouteHandler({ async function processRequest(request: OutgoingRequest): Promise { // Prisma JsonValue doesn't carry a precise shape for this JSON blob. const options = request.qstashOptions as any; - const baseUrl = getEnvVariable("NEXT_PUBLIC_STACK_API_URL"); + const baseUrl = getEnvVariable("NEXT_PUBLIC_SERVER_STACK_API_URL", "") || getEnvVariable("NEXT_PUBLIC_STACK_API_URL"); let fullUrl = new URL(options.url, baseUrl).toString(); @@ -157,7 +157,7 @@ export const GET = createSmartRouteHandler({ function buildUpstashRequest(request: OutgoingRequest): UpstashRequest { // Prisma JsonValue doesn't carry a precise shape for this JSON blob. const options = request.qstashOptions as any; - const baseUrl = getEnvVariable("NEXT_PUBLIC_STACK_API_URL"); + const baseUrl = getEnvVariable("NEXT_PUBLIC_SERVER_STACK_API_URL", "") || getEnvVariable("NEXT_PUBLIC_STACK_API_URL"); let fullUrl = new URL(options.url, baseUrl).toString(); diff --git a/apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx b/apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx index e660c21c7..5e1373149 100644 --- a/apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx +++ b/apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx @@ -1,4 +1,5 @@ import { Prisma } from "@/generated/prisma/client"; +import { overrideEnvironmentConfigOverride } from "@/lib/config"; import { LOCAL_EMULATOR_ADMIN_USER_ID, LOCAL_EMULATOR_ONLY_ENDPOINT_MESSAGE, @@ -58,14 +59,15 @@ async function assertLocalEmulatorOwnerTeamReadiness() { } } -async function getOrCreateLocalEmulatorProjectId(absoluteFilePath: string): Promise { +async function getOrCreateLocalEmulatorProjectId(absoluteFilePath: string): Promise<{ projectId: string, created: boolean }> { const existingRows = await globalPrismaClient.$queryRaw(Prisma.sql` SELECT "projectId" FROM "LocalEmulatorProject" WHERE "absoluteFilePath" = ${absoluteFilePath} LIMIT 1 `); - const projectId = existingRows[0] ? existingRows[0].projectId : generateUuid(); + const existingRow = existingRows.length > 0 ? existingRows[0] : undefined; + const projectId = existingRow ? existingRow.projectId : generateUuid(); await globalPrismaClient.project.upsert({ where: { @@ -98,6 +100,25 @@ async function getOrCreateLocalEmulatorProjectId(absoluteFilePath: string): Prom }, }); + const created = existingRow === undefined; + + // Seed environment-level defaults BEFORE registering as a LocalEmulatorProject: + // once registered, setEnvironmentConfigOverride refuses to write. + // - domains.allowLocalhost: fresh emulator projects allow localhost redirects + // so developers don't hit "Redirect URL not whitelisted" before configuring + // trustedDomains. + // - payments.testMode: emulator payments always go through stripe-mock. + if (created) { + await overrideEnvironmentConfigOverride({ + projectId, + branchId: DEFAULT_BRANCH_ID, + environmentConfigOverrideOverride: { + "domains.allowLocalhost": true, + "payments.testMode": true, + }, + }); + } + await globalPrismaClient.$executeRaw(Prisma.sql` INSERT INTO "LocalEmulatorProject" ("absoluteFilePath", "projectId", "createdAt", "updatedAt") VALUES (${absoluteFilePath}, ${projectId}, NOW(), NOW()) @@ -107,7 +128,7 @@ async function getOrCreateLocalEmulatorProjectId(absoluteFilePath: string): Prom "updatedAt" = NOW() `); - return projectId; + return { projectId, created }; } async function getOrCreateCredentials(projectId: string) { @@ -142,7 +163,7 @@ async function getOrCreateCredentials(projectId: string) { }, }); - if (!keySet.secretServerKey || !keySet.superSecretAdminKey) { + if (!keySet.publishableClientKey || !keySet.secretServerKey || !keySet.superSecretAdminKey) { throw new StackAssertionError("Local emulator key set is missing required keys.", { projectId, keySetId: keySet.id, @@ -150,6 +171,7 @@ async function getOrCreateCredentials(projectId: string) { } return { + publishableClientKey: keySet.publishableClientKey, secretServerKey: keySet.secretServerKey, superSecretAdminKey: keySet.superSecretAdminKey, }; @@ -179,6 +201,7 @@ export const POST = createSmartRouteHandler({ bodyType: yupString().oneOf(["json"]).defined(), body: yupObject({ project_id: yupString().defined(), + publishable_client_key: yupString().defined(), secret_server_key: yupString().defined(), super_secret_admin_key: yupString().defined(), branch_config_override_string: yupString().defined(), @@ -215,7 +238,7 @@ export const POST = createSmartRouteHandler({ await assertLocalEmulatorOwnerTeamReadiness(); - const projectId = await getOrCreateLocalEmulatorProjectId(absoluteFilePath); + const { projectId } = await getOrCreateLocalEmulatorProjectId(absoluteFilePath); const credentials = await getOrCreateCredentials(projectId); const fileConfig = await readConfigFromFile(absoluteFilePath); @@ -224,6 +247,7 @@ export const POST = createSmartRouteHandler({ bodyType: "json" as const, body: { project_id: projectId, + publishable_client_key: credentials.publishableClientKey, secret_server_key: credentials.secretServerKey, super_secret_admin_key: credentials.superSecretAdminKey, branch_config_override_string: JSON.stringify(fileConfig), diff --git a/apps/backend/src/lib/ai/models.ts b/apps/backend/src/lib/ai/models.ts index 5635f4f8a..71693d6d7 100644 --- a/apps/backend/src/lib/ai/models.ts +++ b/apps/backend/src/lib/ai/models.ts @@ -1,3 +1,4 @@ +import { isLocalEmulatorEnabled } from "@/lib/local-emulator"; import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; @@ -59,7 +60,7 @@ export const ALLOWED_MODEL_IDS: ReadonlySet = new Set([ ]); export function createOpenRouterProvider() { - const baseURL = getNodeEnvironment() === "development" + const baseURL = (getNodeEnvironment() === "development" || isLocalEmulatorEnabled()) ? "http://localhost:8102/api/latest/integrations/ai-proxy/v1" : "https://api.stack-auth.com/api/latest/integrations/ai-proxy/v1"; return createOpenRouter({ diff --git a/apps/backend/src/lib/js-execution.tsx b/apps/backend/src/lib/js-execution.tsx index 0a56cda58..b02b822e3 100644 --- a/apps/backend/src/lib/js-execution.tsx +++ b/apps/backend/src/lib/js-execution.tsx @@ -1,6 +1,7 @@ import { traceSpan } from '@/utils/telemetry'; import { runAsynchronouslyAndWaitUntil } from '@/utils/background-tasks'; import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env'; +import { isLocalEmulatorEnabled } from "@/lib/local-emulator"; import { StackAssertionError, captureError } from '@stackframe/stack-shared/dist/utils/errors'; import { Result } from '@stackframe/stack-shared/dist/utils/results'; import { Sandbox } from '@vercel/sandbox'; @@ -27,11 +28,13 @@ function createFreestyleEngine(): JsEngine { let baseUrl = getEnvVariable("STACK_FREESTYLE_API_ENDPOINT", "") || undefined; if (apiKey === "mock_stack_freestyle_key") { - if (!["development", "test"].includes(getNodeEnvironment())) { + if (!["development", "test"].includes(getNodeEnvironment()) && !isLocalEmulatorEnabled()) { throw new StackAssertionError("Mock Freestyle key used in production; please set the STACK_FREESTYLE_API_KEY environment variable."); } - const prefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); - baseUrl = `http://localhost:${prefix}22`; + if (!baseUrl) { + const prefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); + baseUrl = `http://localhost:${prefix}22`; + } } const freestyle = new FreestyleClient({ @@ -147,7 +150,7 @@ export async function executeJavascript(code: string, options: ExecuteJavascript return await runWithFallback(code, options); } else { - if (getNodeEnvironment().includes("prod")) { + if (getNodeEnvironment().includes("prod") && !isLocalEmulatorEnabled()) { throw new StackAssertionError("STACK_VERCEL_SANDBOX_TOKEN is set to the disabled sentinel value in production. Please configure a real Vercel Sandbox token."); } diff --git a/apps/backend/src/lib/payments.tsx b/apps/backend/src/lib/payments.tsx index 4d14b9e23..31a20203b 100644 --- a/apps/backend/src/lib/payments.tsx +++ b/apps/backend/src/lib/payments.tsx @@ -7,7 +7,6 @@ import type { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/use import type { inlineProductSchema, productSchema, productSchemaWithMetadata } from "@stackframe/stack-shared/dist/schema-fields"; import { SUPPORTED_CURRENCIES } from "@stackframe/stack-shared/dist/utils/currency-constants"; import { FAR_FUTURE_DATE, addInterval, getIntervalsElapsed } from "@stackframe/stack-shared/dist/utils/dates"; -import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; import { StackAssertionError, StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; import { filterUndefined, getOrUndefined, has, typedEntries, typedFromEntries, typedKeys, typedValues } from "@stackframe/stack-shared/dist/utils/objects"; import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings"; @@ -15,11 +14,9 @@ import { isUuid } from "@stackframe/stack-shared/dist/utils/uuids"; import Stripe from "stripe"; import * as yup from "yup"; import { Tenancy } from "./tenancies"; -import { getStripeForAccount } from "./stripe"; +import { getStripeForAccount, useStripeMock } from "./stripe"; const DEFAULT_PRODUCT_START_DATE = new Date("1973-01-01T12:00:00.000Z"); // monday -const stripeSecretKey = getEnvVariable("STACK_STRIPE_SECRET_KEY", ""); -const useStripeMock = stripeSecretKey === "sk_test_mockstripekey" && ["development", "test"].includes(getNodeEnvironment()); type Product = yup.InferType; type ProductWithMetadata = yup.InferType; diff --git a/apps/backend/src/lib/stripe.tsx b/apps/backend/src/lib/stripe.tsx index e722b45b1..c664f82ab 100644 --- a/apps/backend/src/lib/stripe.tsx +++ b/apps/backend/src/lib/stripe.tsx @@ -8,15 +8,18 @@ import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dis import { captureError, StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; import Stripe from "stripe"; import type * as yup from "yup"; +import { isLocalEmulatorEnabled } from "./local-emulator"; import { createStripeProxy, type StripeOverridesMap } from "./stripe-proxy"; const stripeSecretKey = getEnvVariable("STACK_STRIPE_SECRET_KEY", ""); -const useStripeMock = stripeSecretKey === "sk_test_mockstripekey" && ["development", "test"].includes(getNodeEnvironment()); +export const useStripeMock = isLocalEmulatorEnabled() + || (stripeSecretKey === "sk_test_mockstripekey" && ["development", "test"].includes(getNodeEnvironment())); const stackPortPrefix = getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"); +const stripeMockPort = Number(getEnvVariable("STACK_STRIPE_MOCK_PORT", "") || `${stackPortPrefix}23`); const stripeConfig: Stripe.StripeConfig = useStripeMock ? { protocol: "http", host: "localhost", - port: Number(`${stackPortPrefix}23`), + port: stripeMockPort, } : {}; /** Product type as stored in Stripe metadata (same as config product schema) */ diff --git a/apps/backend/src/lib/upstash.tsx b/apps/backend/src/lib/upstash.tsx index 6b4f48fec..e2c752096 100644 --- a/apps/backend/src/lib/upstash.tsx +++ b/apps/backend/src/lib/upstash.tsx @@ -28,6 +28,12 @@ export async function ensureUpstashSignature(fullReq: SmartRequest): Promise provider) .filter((provider): provider is AdminOAuthProviderConfig => !!provider), diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx index 42671d8f7..4aaebe0b2 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx @@ -457,12 +457,10 @@ function NewDraftDropdown({ - - + }> Create from scratch - - + }> Create from template @@ -480,12 +478,10 @@ function NewDraftDropdown({ - - + }> Create from scratch - - + }> Create from template diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsx index 5644dcab4..2d2a211c2 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsx @@ -61,7 +61,9 @@ function PaymentsLayoutInner({ children }: { children: React.ReactNode }) { }); }; - if (!stripeAccountInfo) { + const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true"; + + if (!stripeAccountInfo && !isLocalEmulator) { return (
@@ -172,7 +174,7 @@ function PaymentsLayoutInner({ children }: { children: React.ReactNode }) {
- ) : !stripeAccountInfo.details_submitted && ( + ) : stripeAccountInfo && !stripeAccountInfo.details_submitted && (
)} - {getPublicEnvVar("NEXT_PUBLIC_STACK_IS_PREVIEW") !== "true" && ( + {getPublicEnvVar("NEXT_PUBLIC_STACK_IS_PREVIEW") !== "true" && getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") !== "true" && (
- {isPreview ? ( + {isPreview || isLocalEmulator ? ( - Payouts are unavailable in preview mode. + Payouts are unavailable in {isLocalEmulator ? "the local emulator" : "preview mode"}. ) : ( diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx index bbedefbd7..39f388efd 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx @@ -1630,9 +1630,9 @@ function Draggable(props: {
A runtime error occured while rendering this widget.

-
+ {props.reset && }

{errorToNiceString(props.error)}
diff --git a/apps/dashboard/src/components/commands/ai-chat-shared.tsx b/apps/dashboard/src/components/commands/ai-chat-shared.tsx index ef955001e..75723abb4 100644 --- a/apps/dashboard/src/components/commands/ai-chat-shared.tsx +++ b/apps/dashboard/src/components/commands/ai-chat-shared.tsx @@ -211,8 +211,8 @@ export const ToolInvocationCard = memo(function ToolInvocationCard({ const { label, icon: Icon } = getToolDisplay(); - const input = invocation.input as { query?: string }; - const queryArg = input.query; + const input = invocation.input as { query?: string } | undefined; + const queryArg = input?.query; const result = invocation.output as { success?: boolean, result?: unknown[], error?: string, rowCount?: number }; return ( diff --git a/apps/dashboard/src/components/data-table/api-key-table.tsx b/apps/dashboard/src/components/data-table/api-key-table.tsx index 9f7c142eb..c7938d9c9 100644 --- a/apps/dashboard/src/components/data-table/api-key-table.tsx +++ b/apps/dashboard/src/components/data-table/api-key-table.tsx @@ -1,6 +1,6 @@ 'use client'; import { InternalApiKey } from '@stackframe/stack'; -import { DesignDataTable } from "@/components/design-components"; +import { DesignCard, DesignDataTable } from "@/components/design-components"; import { ActionCell, ActionDialog, BadgeCell, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from "@/components/ui"; import { ColumnDef, Row, Table } from "@tanstack/react-table"; import { useMemo, useState } from "react"; @@ -144,12 +144,13 @@ export function InternalApiKeyTable(props: { apiKeys: InternalApiKey[], showPubl }); }, [props.apiKeys]); - return ; + return + + ; } diff --git a/apps/dashboard/src/components/payments/stripe-connect-provider.tsx b/apps/dashboard/src/components/payments/stripe-connect-provider.tsx index 36d1fc49c..6d57a3f3d 100644 --- a/apps/dashboard/src/components/payments/stripe-connect-provider.tsx +++ b/apps/dashboard/src/components/payments/stripe-connect-provider.tsx @@ -13,6 +13,7 @@ import { useEffect } from "react"; import { appearanceVariablesForTheme } from "./stripe-theme-variables"; const isPreview = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_PREVIEW") === "true"; +const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true"; type StripeConnectProviderProps = { children: React.ReactNode, @@ -36,7 +37,7 @@ export function StripeConnectProvider({ children }: StripeConnectProviderProps) const adminApp = useAdminApp(); const { resolvedTheme } = useTheme(); - const stripeConnectInstance = isPreview ? null : getStripeConnectInstance(adminApp); + const stripeConnectInstance = isPreview || isLocalEmulator ? null : getStripeConnectInstance(adminApp); useEffect(() => { if (!stripeConnectInstance) return; @@ -47,7 +48,7 @@ export function StripeConnectProvider({ children }: StripeConnectProviderProps) }); }, [resolvedTheme, stripeConnectInstance]); - // In preview mode, skip Stripe Connect initialization entirely + // In preview/emulator mode, skip Stripe Connect initialization entirely if (!stripeConnectInstance) { return <>{children}; } diff --git a/apps/dashboard/src/components/repeating-input.tsx b/apps/dashboard/src/components/repeating-input.tsx index 6d09b25fc..d6ced0a44 100644 --- a/apps/dashboard/src/components/repeating-input.tsx +++ b/apps/dashboard/src/components/repeating-input.tsx @@ -166,7 +166,7 @@ export function RepeatingInput({ disabled={disabled || readOnly} className={cn( "rounded-r-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0", - prefix && "pl-7", + prefix && "!pl-7", inputClassName )} /> diff --git a/apps/dev-launchpad/package.json b/apps/dev-launchpad/package.json index 23ea4a23b..ab70c318e 100644 --- a/apps/dev-launchpad/package.json +++ b/apps/dev-launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/dev-launchpad", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "private": true, "scripts": { diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 91801cab5..6dbeab447 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e2e-tests", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "private": true, "type": "module", diff --git a/apps/hosted-components/package.json b/apps/hosted-components/package.json index e0a9a308a..031058657 100644 --- a/apps/hosted-components/package.json +++ b/apps/hosted-components/package.json @@ -1,7 +1,7 @@ { "name": "@stackframe/hosted-components", "private": true, - "version": "2.8.83", + "version": "2.8.84", "type": "module", "scripts": { "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09", diff --git a/apps/mock-oauth-server/package.json b/apps/mock-oauth-server/package.json index fe0142dc2..23ed1b03e 100644 --- a/apps/mock-oauth-server/package.json +++ b/apps/mock-oauth-server/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/mock-oauth-server", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "private": true, "main": "index.js", diff --git a/docker/local-emulator/Dockerfile b/docker/local-emulator/Dockerfile index 3ef9151a5..56deae788 100644 --- a/docker/local-emulator/Dockerfile +++ b/docker/local-emulator/Dockerfile @@ -52,6 +52,7 @@ COPY docs ./docs # https://nextjs.org/docs/pages/api-reference/next-config-js/output ENV NEXT_CONFIG_OUTPUT=standalone +ENV NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=pk_test_mock_publishable_key_for_local_emulator # Build the backend NextJS app RUN pnpm turbo run docker-build --filter=@stackframe/backend... --filter=@stackframe/dashboard... @@ -87,8 +88,47 @@ RUN cp -a /app/node_modules /pruned-node_modules && \ date-fns@2* date-fns@3* +# ── Freestyle mock build ───────────────────────────────────────────────────── + +FROM node-base AS freestyle-mock-builder +WORKDIR /freestyle-mock +COPY docker/dependencies/freestyle-mock/Dockerfile /tmp/freestyle-mock-dockerfile +# Extract the inline package.json and server.mjs from the Dockerfile's RUN cat commands, +# then install dependencies. This avoids duplicating the source. +RUN node -e " \ + const fs = require('fs'); \ + const df = fs.readFileSync('/tmp/freestyle-mock-dockerfile', 'utf8'); \ + const pkgMatch = df.match(/cat <<'EOF' > package\\.json\\n([\\s\\S]*?)\\nEOF/); \ + fs.writeFileSync('package.json', pkgMatch[1]); \ + const srvMatch = df.match(/cat <<'EOF' > server\\.mjs\\n([\\s\\S]*?)\\nEOF/); \ + let server = srvMatch[1]; \ + server = server.replace('server.listen(8080)', 'server.listen(process.env.PORT || 8080)'); \ + server = server.replace( \ + 'from \"fs/promises\"', \ + 'from \"fs/promises\"; import { symlinkSync } from \"fs\"' \ + ); \ + server = server.replace( \ + 'await mkdir(workDir, { recursive: true });', \ + 'await mkdir(workDir, { recursive: true }); try { symlinkSync(\"/app/freestyle-mock/node_modules\", join(workDir, \"node_modules\")); } catch {}' \ + ); \ + fs.writeFileSync('server.mjs', server); \ +" +RUN npm install + + +# ── Mock OAuth server build ─────────────────────────────────────────────────── + +FROM node-base AS mock-oauth-builder +WORKDIR /mock-oauth +COPY apps/mock-oauth-server/package.json . +RUN pnpm install && pnpm add esbuild --save-dev +COPY apps/mock-oauth-server/src ./src +RUN npx esbuild src/index.ts --bundle --platform=node --target=node22 --outfile=dist/index.cjs + + # ── Service binary stages ───────────────────────────────────────────────────── +FROM stripe/stripe-mock:v0.195.0 AS stripe-mock-bin FROM inbucket/inbucket:3.1.0 AS inbucket-bin FROM svix/svix-server:v1.88.0 AS svix-bin FROM clickhouse/clickhouse-server:25.10 AS clickhouse-bin @@ -159,6 +199,9 @@ COPY --from=node-base /usr/local/bin/node /usr/local/bin/node # Inbucket COPY --from=inbucket-bin /opt/inbucket /opt/inbucket +# Stripe mock +COPY --from=stripe-mock-bin /bin/stripe-mock /usr/local/bin/stripe-mock + # Svix (UPX-compressed) COPY --from=upx-compress /out/svix-server /usr/local/bin/svix-server @@ -191,6 +234,14 @@ RUN cp -a /app/node_modules /app/node_modules.standalone 2>/dev/null || mkdir -p COPY --from=migration-pruner /pruned-node_modules ./node_modules COPY --from=builder /app/packages ./packages +# Mock OAuth server (bundled single file) +COPY --from=mock-oauth-builder /mock-oauth/dist/index.cjs /app/mock-oauth-server/index.cjs + +# Freestyle mock (JS execution for email rendering) +COPY --from=freestyle-mock-builder /freestyle-mock /app/freestyle-mock +COPY --from=node-base /usr/local/bin/npm /usr/local/bin/npm +COPY --from=node-base /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm + RUN mkdir -p \ /data/postgres \ /data/redis \ @@ -207,17 +258,18 @@ RUN mkdir -p \ && chown -R postgres:postgres /data/postgres COPY docker/local-emulator/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY docker/local-emulator/run-cron-jobs.sh /run-cron-jobs.sh COPY docker/local-emulator/entrypoint.sh /entrypoint.sh COPY docker/local-emulator/init-services.sh /init-services.sh COPY docker/local-emulator/start-app.sh /start-app.sh COPY docker/local-emulator/clickhouse-config.xml /etc/clickhouse-server/config.xml COPY docker/local-emulator/clickhouse-users.xml /etc/clickhouse-server/users.xml COPY docker/server/entrypoint.sh /app-entrypoint.sh -RUN chmod +x /entrypoint.sh /init-services.sh /start-app.sh /app-entrypoint.sh +RUN chmod +x /entrypoint.sh /init-services.sh /start-app.sh /app-entrypoint.sh /run-cron-jobs.sh # PostgreSQL: 5432, Redis: 6379, Inbucket: 2500/9001/1100, # Svix: 8071, ClickHouse: 8123/9009, MinIO: 9090, QStash: 8080 -# Backend: 8102, Dashboard: 8101 -EXPOSE 5432 6379 2500 9001 1100 8071 8123 9009 9090 8080 8101 8102 +# Backend: 8102, Dashboard: 8101, Mock OAuth: 8114 +EXPOSE 5432 6379 2500 9001 1100 8071 8123 9009 9090 8080 8101 8102 8114 ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/local-emulator/clickhouse-config.xml b/docker/local-emulator/clickhouse-config.xml index 31aa71922..0ba2c03fb 100644 --- a/docker/local-emulator/clickhouse-config.xml +++ b/docker/local-emulator/clickhouse-config.xml @@ -15,6 +15,8 @@ 0.5 + SQL_ + users.xml diff --git a/docker/local-emulator/entrypoint.sh b/docker/local-emulator/entrypoint.sh index daa985465..562cb6795 100644 --- a/docker/local-emulator/entrypoint.sh +++ b/docker/local-emulator/entrypoint.sh @@ -28,4 +28,11 @@ if [ -z "$(ls -A "$PGDATA" 2>/dev/null)" ]; then gosu postgres "$PG_BIN/pg_ctl" -D "$PGDATA" stop -w fi +# Generate a fresh CRON_SECRET per container start. The cron endpoints are +# internal — nothing outside the container calls them — so we don't want the +# baked-in mock value from .env.development to be a usable credential against +# a running emulator. Overriding here propagates to both the backend and the +# run-cron-jobs.sh loop via supervisord's inherited environment. +export CRON_SECRET="$(openssl rand -hex 32)" + exec /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf diff --git a/docker/local-emulator/generate-env-development.mjs b/docker/local-emulator/generate-env-development.mjs index f0b0b20d2..1266c2bae 100644 --- a/docker/local-emulator/generate-env-development.mjs +++ b/docker/local-emulator/generate-env-development.mjs @@ -90,9 +90,11 @@ const entries = [ fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_ALLOW_LOCALHOST"), fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_OAUTH_PROVIDERS"), fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS"), - fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY"), - fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY"), - fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY"), + // STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY is generated per-VM at boot + // by docker/local-emulator/qemu/cloud-init/emulator/user-data and injected via + // /run/stack-auth/local-emulator.env. SECRET_SERVER_KEY and SUPER_SECRET_ADMIN_KEY + // are intentionally omitted so the seed script leaves them null on the internal + // project; per-project credentials come from /api/v1/internal/local-emulator/project. blank(), comment("# Third-party/test integrations"), fromSource("apps/backend/.env.development", backendEnv, "STACK_SVIX_API_KEY"), @@ -159,7 +161,7 @@ const entries = [ literal("STACK_S3_ENDPOINT", "http://127.0.0.1:9090"), literal("STACK_QSTASH_URL", "http://127.0.0.1:8080"), literal("STACK_CLICKHOUSE_URL", "http://127.0.0.1:8123"), - literal("STACK_CLICKHOUSE_DATABASE", "analytics"), + literal("STACK_CLICKHOUSE_DATABASE", "default"), literal("STACK_EMAIL_MONITOR_INBUCKET_API_URL", "http://127.0.0.1:9001"), literal("BACKEND_PORT", "8102"), literal("DASHBOARD_PORT", "8101"), diff --git a/docker/local-emulator/qemu/build-image.sh b/docker/local-emulator/qemu/build-image.sh index 498d16173..f4d91771b 100755 --- a/docker/local-emulator/qemu/build-image.sh +++ b/docker/local-emulator/qemu/build-image.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=common.sh source "$SCRIPT_DIR/common.sh" -IMAGE_DIR="$SCRIPT_DIR/images" +IMAGE_DIR="${EMULATOR_IMAGE_DIR:-$HOME/.stack/emulator/images}" CLOUD_INIT_ROOT="$SCRIPT_DIR/cloud-init" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" diff --git a/docker/local-emulator/qemu/cloud-init/emulator/user-data b/docker/local-emulator/qemu/cloud-init/emulator/user-data index 07b0bc5f4..38fe2b064 100644 --- a/docker/local-emulator/qemu/cloud-init/emulator/user-data +++ b/docker/local-emulator/qemu/cloud-init/emulator/user-data @@ -58,7 +58,7 @@ write_files: #!/bin/bash set -euo pipefail - mkdir -p /mnt/stack-runtime /run/stack-auth + mkdir -p /mnt/stack-runtime /run/stack-auth /var/lib/stack-auth runtime_device="$(readlink -f /dev/disk/by-label/STACKCFG)" mountpoint -q /mnt/stack-runtime || mount -o ro "$runtime_device" /mnt/stack-runtime @@ -67,6 +67,24 @@ write_files: source /mnt/stack-runtime/base.env set +a + # Generate and persist the internal-project keys on first boot; reuse + # across container restarts so the dashboard keeps its internal-project + # session. Reset via `stack emulator reset`. + # + # pck: used by stack-cli to auth against /api/v1/internal/local-emulator/project + # ssk/sak: required by the emulator's own dashboard (StackServerApp + # construction throws without them). Not used by user-app flows; the + # /local-emulator/project route mints separate per-project credentials. + umask 077 + for key in internal-pck internal-ssk internal-sak; do + if [ ! -s "/var/lib/stack-auth/$key" ]; then + openssl rand -hex 32 > "/var/lib/stack-auth/$key" + fi + done + INTERNAL_PCK="$(cat /var/lib/stack-auth/internal-pck)" + INTERNAL_SSK="$(cat /var/lib/stack-auth/internal-ssk)" + INTERNAL_SAK="$(cat /var/lib/stack-auth/internal-sak)" + # Container-local dependencies run on localhost. Host-only development # services (such as the OAuth mock server) are reachable via the QEMU # user-network host alias. @@ -78,6 +96,9 @@ write_files: # Static vars from base config and runtime (e.g. API keys, feature flags) cat /mnt/stack-runtime/base.env cat /mnt/stack-runtime/runtime.env + printf 'STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=%s\n' "$INTERNAL_PCK" + printf 'STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=%s\n' "$INTERNAL_SSK" + printf 'STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=%s\n' "$INTERNAL_SAK" # Computed vars — depend on port prefix or deps host # Host-side ports (for browser URLs — browser runs on host, not in VM) @@ -108,7 +129,10 @@ write_files: STACK_CLICKHOUSE_URL=http://${DEPS_HOST}:8123 STACK_EMAIL_MONITOR_VERIFICATION_CALLBACK_URL=http://localhost:${HP_DASHBOARD}/handler/email-verification STACK_EMAIL_MONITOR_INBUCKET_API_URL=http://${DEPS_HOST}:9001 - STACK_OAUTH_MOCK_URL=http://${HOST_SERVICES_HOST}:${P}14 + STACK_OAUTH_MOCK_URL=http://localhost:${P}14 + STACK_FREESTYLE_API_ENDPOINT=http://${DEPS_HOST}:8180 + STACK_STRIPE_MOCK_PORT=12111 + NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=pk_test_mock_publishable_key_for_local_emulator BACKEND_PORT=${P}02 DASHBOARD_PORT=${P}01 COMPUTED @@ -135,20 +159,54 @@ write_files: /usr/local/bin/mount-host-fs /usr/local/bin/render-stack-env + + # Publish the internal publishable client key to the host via 9p so the + # stack-cli can authenticate its bootstrap call to + # /api/v1/internal/local-emulator/project. + set -a + source /mnt/stack-runtime/runtime.env + set +a + if [ -n "${STACK_EMULATOR_VM_DIR_HOST:-}" ] && [ -s /var/lib/stack-auth/internal-pck ]; then + install -m 0600 /var/lib/stack-auth/internal-pck \ + "/host${STACK_EMULATOR_VM_DIR_HOST}/internal-pck" + fi + docker rm -f stack >/dev/null 2>&1 || true - exec docker run \ - --rm \ - --name stack \ - --network host \ - --add-host host.docker.internal:host-gateway \ - --env-file /run/stack-auth/local-emulator.env \ - -v stack-postgres-data:/data/postgres \ - -v stack-redis-data:/data/redis \ - -v stack-clickhouse-data:/data/clickhouse \ - -v stack-minio-data:/data/minio \ - -v stack-inbucket-data:/data/inbucket \ - -v /host:/host \ - stack-local-emulator + + # Mirror container stdout/stderr to a host-visible log for debugging. + # The container already bind-mounts /host:/host, so we reuse that path. + # Falls back to stdout (captured by systemd-journald) when no host log is set. + if [ -n "${STACK_EMULATOR_VM_DIR_HOST:-}" ]; then + host_log="/host${STACK_EMULATOR_VM_DIR_HOST}/stack.log" + : > "$host_log" 2>/dev/null || true + exec docker run \ + --rm \ + --name stack \ + --network host \ + --add-host host.docker.internal:host-gateway \ + --env-file /run/stack-auth/local-emulator.env \ + -v stack-postgres-data:/data/postgres \ + -v stack-redis-data:/data/redis \ + -v stack-clickhouse-data:/data/clickhouse \ + -v stack-minio-data:/data/minio \ + -v stack-inbucket-data:/data/inbucket \ + -v /host:/host \ + stack-local-emulator 2>&1 | tee -a "$host_log" + else + exec docker run \ + --rm \ + --name stack \ + --network host \ + --add-host host.docker.internal:host-gateway \ + --env-file /run/stack-auth/local-emulator.env \ + -v stack-postgres-data:/data/postgres \ + -v stack-redis-data:/data/redis \ + -v stack-clickhouse-data:/data/clickhouse \ + -v stack-minio-data:/data/minio \ + -v stack-inbucket-data:/data/inbucket \ + -v /host:/host \ + stack-local-emulator + fi - path: /usr/local/bin/wait-for-deps permissions: '0755' @@ -231,7 +289,7 @@ write_files: NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL=http://127.0.0.1:8101 NEXT_PUBLIC_STACK_SVIX_SERVER_URL=http://localhost:8071 NEXT_PUBLIC_STACK_PORT_PREFIX=81 - STACK_CLICKHOUSE_DATABASE=analytics + STACK_CLICKHOUSE_DATABASE=default BACKEND_PORT=8102 DASHBOARD_PORT=8101 @@ -369,10 +427,23 @@ write_files: log "Skipping smoke test: build arch is arm64 and cross-arch TCG can't reliably run the backend." else log "Running smoke test on slim image..." + # build.env sets NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true, which makes + # docker/server/entrypoint.sh require the three internal SEED keys. + # At real-VM boot those come from render-stack-env via + # /run/stack-auth/local-emulator.env, but that path doesn't run during + # the build-time smoke test. Mint throwaway hex keys for this container + # only; they must be hex because entrypoint.sh also validates that + # before the internal ApiKeySet bootstrap SQL. + SMOKE_PCK="$(openssl rand -hex 32)" + SMOKE_SSK="$(openssl rand -hex 32)" + SMOKE_SAK="$(openssl rand -hex 32)" docker run --rm --name smoke-test \ --network host \ --env-file /etc/stack-build.env \ --env-file /etc/stack-build-computed.env \ + -e STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY="$SMOKE_PCK" \ + -e STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY="$SMOKE_SSK" \ + -e STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY="$SMOKE_SAK" \ -e STACK_SKIP_MIGRATIONS=true \ -e STACK_SKIP_SEED_SCRIPT=true \ -e STACK_RUNTIME_WORK_DIR=/app \ diff --git a/docker/local-emulator/qemu/run-emulator.sh b/docker/local-emulator/qemu/run-emulator.sh index 0a82c1b88..ba905ca36 100755 --- a/docker/local-emulator/qemu/run-emulator.sh +++ b/docker/local-emulator/qemu/run-emulator.sh @@ -5,8 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=common.sh source "$SCRIPT_DIR/common.sh" -IMAGE_DIR="$SCRIPT_DIR/images" -RUN_DIR="${EMULATOR_RUN_DIR:-$SCRIPT_DIR/run}" +IMAGE_DIR="${EMULATOR_IMAGE_DIR:-$HOME/.stack/emulator/images}" +RUN_DIR="${EMULATOR_RUN_DIR:-$HOME/.stack/emulator/run}" VM_RAM="${EMULATOR_RAM:-4096}" VM_CPUS="${EMULATOR_CPUS:-4}" @@ -89,6 +89,7 @@ prepare_runtime_config_iso() { printf "STACK_EMULATOR_BACKEND_HOST_PORT=%s\n" "$EMULATOR_BACKEND_PORT" printf "STACK_EMULATOR_MINIO_HOST_PORT=%s\n" "$EMULATOR_MINIO_PORT" printf "STACK_EMULATOR_INBUCKET_HOST_PORT=%s\n" "$EMULATOR_INBUCKET_PORT" + printf "STACK_EMULATOR_VM_DIR_HOST=%s\n" "$VM_DIR" } > "$cfg_dir/runtime.env" cp "$SCRIPT_DIR/../.env.development" "$cfg_dir/base.env" make_iso_from_dir "$cfg_iso" "STACKCFG" "$cfg_dir" @@ -201,10 +202,16 @@ build_qemu_cmd() { local netdev="user,id=net0" # Only expose user-facing services; internal deps stay inside the VM. - netdev+=",hostfwd=tcp::${EMULATOR_DASHBOARD_PORT}-:${PORT_PREFIX}01" - netdev+=",hostfwd=tcp::${EMULATOR_BACKEND_PORT}-:${PORT_PREFIX}02" - netdev+=",hostfwd=tcp::${EMULATOR_MINIO_PORT}-:9090" - netdev+=",hostfwd=tcp::${EMULATOR_INBUCKET_PORT}-:9001" + # Bind to 127.0.0.1 so the emulator is not reachable from the LAN. + netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_DASHBOARD_PORT}-:${PORT_PREFIX}01" + netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_BACKEND_PORT}-:${PORT_PREFIX}02" + netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_MINIO_PORT}-:9090" + netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_INBUCKET_PORT}-:9001" + # Mock OAuth server: browser redirects land on `localhost:${PORT_PREFIX}14` + # (backend sets STACK_OAUTH_MOCK_URL to that value), so we forward host:port + # ↔ VM:port on the same number. Collides with pnpm dev, but the two modes + # are mutually exclusive. + netdev+=",hostfwd=tcp:127.0.0.1:${PORT_PREFIX}14-:${PORT_PREFIX}14" QEMU_CMD=( "$qemu_bin" @@ -249,7 +256,7 @@ tail_vm_logs() { } ensure_ports_free() { - local ports=("$EMULATOR_DASHBOARD_PORT" "$EMULATOR_BACKEND_PORT" "$EMULATOR_MINIO_PORT" "$EMULATOR_INBUCKET_PORT") + local ports=("$EMULATOR_DASHBOARD_PORT" "$EMULATOR_BACKEND_PORT" "$EMULATOR_MINIO_PORT" "$EMULATOR_INBUCKET_PORT" "${PORT_PREFIX}14") local port for port in "${ports[@]}"; do if lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then diff --git a/docker/local-emulator/run-cron-jobs.sh b/docker/local-emulator/run-cron-jobs.sh new file mode 100755 index 000000000..a30cf03e6 --- /dev/null +++ b/docker/local-emulator/run-cron-jobs.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Polls backend cron endpoints in parallel background loops, matching vercel.json cron config. +# Replaces the tsx scripts used in dev mode since tsx is not in the final image. + +set -e + +BACKEND_URL="http://127.0.0.1:${BACKEND_PORT:-8102}" + +if [ -z "${CRON_SECRET:-}" ]; then + echo "CRON_SECRET is not set; refusing to start cron loops." >&2 + exit 1 +fi + +# Wait for the backend to be ready +until curl -fsS "${BACKEND_URL}/health" >/dev/null 2>&1; do sleep 2; done + +echo "Cron jobs started." + +run_loop() { + local endpoint="$1" + while true; do + curl -sf -o /dev/null --max-time 120 "${BACKEND_URL}${endpoint}" \ + -H "Authorization: Bearer ${CRON_SECRET}" || true + sleep 1 + done +} + +run_loop "/api/latest/internal/email-queue-step" & +run_loop "/api/latest/internal/external-db-sync/sequencer" & +run_loop "/api/latest/internal/external-db-sync/poller" & + +wait diff --git a/docker/local-emulator/supervisord.conf b/docker/local-emulator/supervisord.conf index e8b1fc478..32890bfe7 100644 --- a/docker/local-emulator/supervisord.conf +++ b/docker/local-emulator/supervisord.conf @@ -50,7 +50,8 @@ environment= INBUCKET_WEB_ADDR="0.0.0.0:9001", INBUCKET_POP3_ADDR="0.0.0.0:1100", INBUCKET_STORAGE_TYPE="file", - INBUCKET_STORAGE_PARAMS="path:/data/inbucket" + INBUCKET_STORAGE_PARAMS="path:/data/inbucket", + INBUCKET_WEB_UIDIR="/opt/inbucket/ui" autostart=true autorestart=true priority=20 @@ -120,6 +121,43 @@ stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 +; --- Stripe mock --- + +[program:stripe-mock] +command=/usr/local/bin/stripe-mock -port 12111 +autostart=true +autorestart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; --- Freestyle mock (JS execution for email rendering) --- + +[program:freestyle-mock] +command=/usr/local/bin/node /app/freestyle-mock/server.mjs +environment=NODE_PATH="/app/freestyle-mock/node_modules",PORT="8180" +autostart=true +autorestart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; --- Mock OAuth server --- + +[program:mock-oauth] +command=/usr/local/bin/node /app/mock-oauth-server/index.cjs +autostart=true +autorestart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + ; --- Post-startup init --- [program:init-services] @@ -134,6 +172,19 @@ stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 +; --- Cron jobs (email queue, external DB sync) --- + +[program:cron-jobs] +command=/run-cron-jobs.sh +autostart=true +autorestart=true +startsecs=0 +priority=70 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + ; --- Stack Auth backend + dashboard --- [program:stack-app] diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index da7214a01..659eb9628 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -11,14 +11,28 @@ fi # ============= ENV VARS ============= -export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-$(openssl rand -base64 32)} -export STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-$(openssl rand -base64 32)} -export STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-$(openssl rand -base64 32)} +if [ "$NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR" = "true" ]; then + for v in STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY; do + if [ -z "${!v:-}" ]; then + echo "$v must be set in local-emulator mode (injected by the QEMU VM)." >&2 + exit 1 + fi + done + export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY +else + export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-$(openssl rand -base64 32)} + export STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-$(openssl rand -base64 32)} + export STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-$(openssl rand -base64 32)} +fi export NEXT_PUBLIC_STACK_PROJECT_ID=internal export NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY} -export STACK_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY} -export STACK_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY} +if [ -n "${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-}" ]; then + export STACK_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY} +fi +if [ -n "${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-}" ]; then + export STACK_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY} +fi export NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL=${NEXT_PUBLIC_STACK_DASHBOARD_URL} export NEXT_PUBLIC_STACK_PORT_PREFIX=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81} @@ -65,6 +79,44 @@ else cd ../.. fi +# ============= LOCAL EMULATOR: BOOTSTRAP INTERNAL API KEY SET ============= +# The build-time seed ran without any keys (the VM generates random ones on +# first boot). The slim image strips apps/backend/dist so we can't re-run the +# full seed here. Instead, targeted-upsert the internal api key set with the +# VM-supplied keys: +# - pck: used by stack-cli to auth against /api/v1/internal/local-emulator/project +# - ssk/sak: required by the emulator's own dashboard (StackServerApp ctor +# throws without ssk). User-app flows don't use these — per-project +# credentials come from the /local-emulator/project route. +if [ "$NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR" = "true" ] && [ -n "${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-}" ] && [ -n "${STACK_DATABASE_CONNECTION_STRING:-}" ]; then + # Validate the keys are hex-only to defuse any SQL-injection risk (the VM + # generates them via `openssl rand -hex 32`, so this is an assert, not a filter). + for varname in STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY; do + val="${!varname:-}" + if [ -z "$val" ]; then + echo "ERROR: $varname is not set; refusing to bootstrap internal api key set." >&2 + exit 1 + fi + if ! printf '%s' "$val" | grep -Eq '^[0-9a-fA-F]+$'; then + echo "ERROR: $varname is not hex-only; refusing to bootstrap internal api key set." >&2 + exit 1 + fi + done + echo "Bootstrapping internal API key set (emulator runtime)..." + psql "$STACK_DATABASE_CONNECTION_STRING" -v ON_ERROR_STOP=1 < { + const path = internalPckPath(); + const deadline = Date.now() + timeoutMs; + let delay = 250; + while (Date.now() < deadline) { + if (existsSync(path)) { + const contents = readFileSync(path, "utf-8").trim(); + if (contents) return contents; + } + await new Promise((r) => setTimeout(r, delay)); + delay = Math.min(delay * 2, 2000); + } + throw new CliError(`Timed out waiting for emulator internal publishable client key at ${path}`); +} + +type EmulatorCredentials = { + project_id: string, + publishable_client_key: string, + secret_server_key: string, +}; + +async function fetchEmulatorCredentials(pck: string, backendPort: number, configFile: string): Promise { + const url = `http://127.0.0.1:${backendPort}/api/v1/internal/local-emulator/project`; + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Stack-Project-Id": "internal", + "X-Stack-Access-Type": "client", + "X-Stack-Publishable-Client-Key": pck, + }, + body: JSON.stringify({ absolute_file_path: configFile }), + }); + if (!res.ok) { + throw new CliError(`Failed to initialize local emulator project (${res.status}): ${await res.text()}`); + } + const data = await res.json() as { + project_id: string, + publishable_client_key: string, + secret_server_key: string, + }; + return { + project_id: data.project_id, + publishable_client_key: data.publishable_client_key, + secret_server_key: data.secret_server_key, + }; +} + function gh(args: string[]): string { try { return execFileSync("gh", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim(); @@ -15,27 +93,63 @@ function gh(args: string[]): string { } } -function findQemuDir(): string { - for (const rel of ["docker/local-emulator/qemu", "../docker/local-emulator/qemu"]) { - const dir = resolve(process.cwd(), rel); - if (existsSync(join(dir, "run-emulator.sh"))) return dir; - } - throw new CliError("Could not find QEMU emulator directory. Run this from the stack-auth repo root."); +function emulatorScriptsDir(): string { + const here = dirname(fileURLToPath(import.meta.url)); + const bundled = join(here, "emulator"); + if (existsSync(join(bundled, "run-emulator.sh"))) return bundled; + const repo = resolve(here, "../../../docker/local-emulator/qemu"); + if (existsSync(join(repo, "run-emulator.sh"))) return repo; + throw new CliError("Emulator scripts not found in CLI bundle."); +} + +function emulatorSpawnEnv(extra?: Record): NodeJS.ProcessEnv { + return { + ...process.env, + EMULATOR_RUN_DIR: emulatorRunDir(), + EMULATOR_IMAGE_DIR: emulatorImageDir(), + ...extra, + }; } function runEmulator(action: string, env?: Record): Promise { - const qemuDir = findQemuDir(); - return new Promise((resolve, reject) => { - const child = spawn(join(qemuDir, "run-emulator.sh"), [action], { + const scriptsDir = emulatorScriptsDir(); + mkdirSync(emulatorRunDir(), { recursive: true }); + mkdirSync(emulatorImageDir(), { recursive: true }); + return new Promise((resolvePromise, reject) => { + const child = spawn(join(scriptsDir, "run-emulator.sh"), [action], { stdio: "inherit", - env: { ...process.env, ...env }, - cwd: qemuDir, + env: emulatorSpawnEnv(env), + cwd: scriptsDir, }); - child.on("close", (code) => code === 0 ? resolve() : reject(new CliError(`run-emulator.sh ${action} exited with code ${code}`))); + child.on("close", (code) => code === 0 ? resolvePromise() : reject(new CliError(`run-emulator.sh ${action} exited with code ${code}`))); child.on("error", (err) => reject(new CliError(`Failed to run run-emulator.sh: ${err.message}`))); }); } +function isEmulatorRunning(): boolean { + const scriptsDir = emulatorScriptsDir(); + try { + execFileSync(join(scriptsDir, "run-emulator.sh"), ["status"], { + stdio: "pipe", + cwd: scriptsDir, + env: emulatorSpawnEnv(), + }); + return true; + } catch { + return false; + } +} + +async function startEmulator(arch: "arm64" | "amd64"): Promise { + mkdirSync(emulatorImageDir(), { recursive: true }); + const img = join(emulatorImageDir(), `stack-emulator-${arch}.qcow2`); + if (!existsSync(img)) { + console.log("No emulator image found. Pulling latest..."); + pullRelease(arch); + } + await runEmulator("start", { EMULATOR_ARCH: arch }); +} + function resolveArch(raw?: string): "arm64" | "amd64" { const arch = raw ?? (process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "amd64" : null); if (arch === "arm64" || arch === "amd64") return arch; @@ -47,7 +161,7 @@ function pullRelease(arch: "arm64" | "amd64", opts: { repo?: string; branch?: st const branch = opts.branch ?? "dev"; const tag = opts.tag ?? `emulator-${branch}-latest`; const asset = `stack-emulator-${arch}.qcow2`; - const imageDir = join(findQemuDir(), "images"); + const imageDir = emulatorImageDir(); mkdirSync(imageDir, { recursive: true }); const dest = join(imageDir, asset); const tmpDest = `${dest}.download`; @@ -89,7 +203,7 @@ export function registerEmulatorCommand(program: Command) { runId = String(runs[0].databaseId); } - const imageDir = join(findQemuDir(), "images"); + const imageDir = emulatorImageDir(); mkdirSync(imageDir, { recursive: true }); const dest = join(imageDir, `stack-emulator-${arch}.qcow2`); if (existsSync(dest)) unlinkSync(dest); @@ -110,14 +224,91 @@ export function registerEmulatorCommand(program: Command) { .command("start") .description("Start the emulator in the background (auto-pulls the latest image if none exists)") .option("--arch ", "Target architecture (default: current system arch). Non-native uses software emulation and is significantly slower.") - .action(async (opts) => { + .option("--config-file ", "Path to a config file; when set, credentials for this project are printed to stdout as JSON") + .action(async (opts: { arch?: string, configFile?: string }) => { const arch = resolveArch(opts.arch); - const img = join(findQemuDir(), "images", `stack-emulator-${arch}.qcow2`); - if (!existsSync(img)) { - console.log("No emulator image found. Pulling latest..."); - pullRelease(arch); + + let resolvedConfigFile: string | undefined; + if (opts.configFile) { + resolvedConfigFile = resolve(opts.configFile); + if (!existsSync(resolvedConfigFile)) { + throw new CliError(`Config file not found: ${resolvedConfigFile}`); + } } - await runEmulator("start", { EMULATOR_ARCH: arch }); + + if (isEmulatorRunning()) { + console.warn("Emulator already running, reusing existing instance."); + } else { + await startEmulator(arch); + } + + if (resolvedConfigFile) { + const pck = await readInternalPck(); + const creds = await fetchEmulatorCredentials(pck, emulatorBackendPort(), resolvedConfigFile); + console.log(JSON.stringify(creds, null, 2)); + } + }); + + emulator + .command("run") + .description("Start the emulator, run a command, and stop the emulator when the command exits") + .argument("", "Command to run (e.g. \"npm run dev\")") + .option("--arch ", "Target architecture") + .option("--config-file ", "Path to a config file; fetches credentials and injects STACK_PROJECT_ID / STACK_PUBLISHABLE_CLIENT_KEY / STACK_SECRET_SERVER_KEY into the child") + .action(async (cmd: string, opts: { arch?: string, configFile?: string }) => { + const arch = resolveArch(opts.arch); + + let resolvedConfigFile: string | undefined; + if (opts.configFile) { + resolvedConfigFile = resolve(opts.configFile); + if (!existsSync(resolvedConfigFile)) { + throw new CliError(`Config file not found: ${resolvedConfigFile}`); + } + } + + const alreadyRunning = isEmulatorRunning(); + if (alreadyRunning) { + console.log("Emulator already running, reusing existing instance."); + } else { + await startEmulator(arch); + } + + const childEnv: Record = { ...process.env as Record }; + if (resolvedConfigFile) { + const pck = await readInternalPck(); + const backendPort = emulatorBackendPort(); + const creds = await fetchEmulatorCredentials(pck, backendPort, resolvedConfigFile); + const apiUrl = `http://127.0.0.1:${backendPort}`; + childEnv.STACK_PROJECT_ID = creds.project_id; + childEnv.NEXT_PUBLIC_STACK_PROJECT_ID = creds.project_id; + childEnv.STACK_PUBLISHABLE_CLIENT_KEY = creds.publishable_client_key; + childEnv.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY = creds.publishable_client_key; + childEnv.STACK_SECRET_SERVER_KEY = creds.secret_server_key; + childEnv.STACK_API_URL = apiUrl; + childEnv.NEXT_PUBLIC_STACK_API_URL = apiUrl; + } + + const child = spawn(cmd, { shell: true, stdio: "inherit", env: childEnv }); + + const forward = (signal: NodeJS.Signals) => () => child.kill(signal); + const onSigint = forward("SIGINT"); + const onSigterm = forward("SIGTERM"); + process.on("SIGINT", onSigint); + process.on("SIGTERM", onSigterm); + + child.on("close", (code) => { + process.off("SIGINT", onSigint); + process.off("SIGTERM", onSigterm); + const exitCode = code ?? 1; + if (alreadyRunning) { + process.exit(exitCode); + } else { + console.log("\nStopping emulator..."); + runEmulator("stop") + .catch(() => { /* best-effort stop */ }) + .finally(() => process.exit(exitCode)); + } + }); }); emulator.command("stop").description("Stop the emulator (data preserved; use 'reset' to clear)").action(() => runEmulator("stop")); diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json index 38a75dbfe..31cdbe003 100644 --- a/packages/stack-sc/package.json +++ b/packages/stack-sc/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-sc", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "exports": { "./force-react-server": { diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index f84bd0ee3..ea80af220 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-shared", - "version": "2.8.83", + "version": "2.8.84", "repository": "https://github.com/stack-auth/stack-auth", "scripts": { "build": "rimraf dist && tsdown", diff --git a/packages/stack-shared/src/interface/client-interface.ts b/packages/stack-shared/src/interface/client-interface.ts index 727965158..6942da804 100644 --- a/packages/stack-shared/src/interface/client-interface.ts +++ b/packages/stack-shared/src/interface/client-interface.ts @@ -465,6 +465,7 @@ export class StackClientInterface { session: InternalSession | null, requestType: "client" | "server" | "admin" = "client", apiUrlOverride?: string, + retryOptions?: { maxAttempts?: number, skipDiagnostics?: boolean }, ) { session ??= this.createSession({ refreshToken: null, @@ -472,19 +473,20 @@ export class StackClientInterface { if (apiUrlOverride) { return await this._networkRetry( - () => this.sendClientRequestInner(path, requestOptions, session!, requestType, apiUrlOverride), - session, - requestType, - ); - } - - return await this._withFallback(async (apiUrl, retryOptions) => { - return await this._networkRetry( - () => this.sendClientRequestInner(path, requestOptions, session!, requestType, apiUrl), + () => this.sendClientRequestInner(path, requestOptions, session!, requestType, apiUrlOverride, retryOptions), session, requestType, retryOptions, ); + } + + return await this._withFallback(async (apiUrl, fallbackRetryOptions) => { + return await this._networkRetry( + () => this.sendClientRequestInner(path, requestOptions, session!, requestType, apiUrl, retryOptions), + session, + requestType, + { ...fallbackRetryOptions, ...retryOptions }, + ); }); } @@ -513,6 +515,7 @@ export class StackClientInterface { session, "client", this.getAnalyticsApiUrl(), + { maxAttempts: 1, skipDiagnostics: true }, ); return Result.ok(response); } catch (e) { @@ -537,6 +540,7 @@ export class StackClientInterface { session, "client", this.getAnalyticsApiUrl(), + { maxAttempts: 1, skipDiagnostics: true }, ); return Result.ok(response); } catch (e) { @@ -576,6 +580,7 @@ export class StackClientInterface { session: InternalSession, requestType: "client" | "server" | "admin", apiUrlOverride?: string, + innerOptions?: { skipDiagnostics?: boolean }, ): Promise, extraOptions?: { uniqueIdentifier?: string, checkString?: string, interface?: StackAdminInterface }) { const resolvedOptions = resolveConstructorOptions(options); + const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey(); super(resolvedOptions, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3aaeaedea..d0ed56e55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,7 +188,7 @@ importers: version: 1.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@sentry/nextjs': specifier: ^10.45.0 - version: 10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2)) + version: 10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2)) '@simplewebauthn/server': specifier: ^13.3.0 version: 13.3.0 @@ -244,8 +244,8 @@ importers: specifier: ^1.0.6 version: 1.0.6 next: - specifier: 16.2.2 - version: 16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 16.1.7 + version: 16.1.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nodemailer: specifier: ^6.9.10 version: 6.9.13 @@ -477,7 +477,7 @@ importers: version: 2.0.2(react@19.2.3) '@sentry/nextjs': specifier: ^10.11.0 - version: 10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2)) + version: 10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2)) '@stackframe/dashboard-ui-components': specifier: workspace:* version: link:../../packages/dashboard-ui-components @@ -507,10 +507,10 @@ importers: version: 3.13.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 1.3.1(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@vercel/speed-insights': specifier: ^1.0.12 - version: 1.0.12(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 1.0.12(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) ai: specifier: ^6.0.0 version: 6.0.81(zod@4.1.12) @@ -540,7 +540,7 @@ importers: version: 1.4.0 geist: specifier: ^1 - version: 1.3.0(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + version: 1.3.0(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) input-otp: specifier: ^1.4.1 version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -551,11 +551,11 @@ importers: specifier: ^4.17.21 version: 4.17.21 next: - specifier: 16.2.2 - version: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 16.1.7 + version: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 0.2.1(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) posthog-js: specifier: ^1.336.1 version: 1.336.1 @@ -5654,6 +5654,9 @@ packages: '@next/env@16.1.5': resolution: {integrity: sha512-CRSCPJiSZoi4Pn69RYBDI9R7YK2g59vLexPQFXY0eyw+ILevIenCywzg+DqmlBik9zszEnw2HLFOUlLAcJbL7g==} + '@next/env@16.1.7': + resolution: {integrity: sha512-rJJbIdJB/RQr2F1nylZr/PJzamvNNhfr3brdKP6s/GW850jbtR70QlSfFselvIBbcPUOlQwBakexjFzqLzF6pg==} + '@next/env@16.2.2': resolution: {integrity: sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==} @@ -5687,6 +5690,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@16.1.7': + resolution: {integrity: sha512-b2wWIE8sABdyafc4IM8r5Y/dS6kD80JRtOGrUiKTsACFQfWWgUQ2NwoUX1yjFMXVsAwcQeNpnucF2ZrujsBBPg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-arm64@16.2.2': resolution: {integrity: sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==} engines: {node: '>= 10'} @@ -5711,6 +5720,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@16.1.7': + resolution: {integrity: sha512-zcnVaaZulS1WL0Ss38R5Q6D2gz7MtBu8GZLPfK+73D/hp4GFMrC2sudLky1QibfV7h6RJBJs/gOFvYP0X7UVlQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-darwin-x64@16.2.2': resolution: {integrity: sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==} engines: {node: '>= 10'} @@ -5738,6 +5753,13 @@ packages: os: [linux] libc: [glibc] + '@next/swc-linux-arm64-gnu@16.1.7': + resolution: {integrity: sha512-2ant89Lux/Q3VyC8vNVg7uBaFVP9SwoK2jJOOR0L8TQnX8CAYnh4uctAScy2Hwj2dgjVHqHLORQZJ2wH6VxhSQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@next/swc-linux-arm64-gnu@16.2.2': resolution: {integrity: sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==} engines: {node: '>= 10'} @@ -5766,6 +5788,13 @@ packages: os: [linux] libc: [musl] + '@next/swc-linux-arm64-musl@16.1.7': + resolution: {integrity: sha512-uufcze7LYv0FQg9GnNeZ3/whYfo+1Q3HnQpm16o6Uyi0OVzLlk2ZWoY7j07KADZFY8qwDbsmFnMQP3p3+Ftprw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + '@next/swc-linux-arm64-musl@16.2.2': resolution: {integrity: sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==} engines: {node: '>= 10'} @@ -5794,6 +5823,13 @@ packages: os: [linux] libc: [glibc] + '@next/swc-linux-x64-gnu@16.1.7': + resolution: {integrity: sha512-KWVf2gxYvHtvuT+c4MBOGxuse5TD7DsMFYSxVxRBnOzok/xryNeQSjXgxSv9QpIVlaGzEn/pIuI6Koosx8CGWA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + '@next/swc-linux-x64-gnu@16.2.2': resolution: {integrity: sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==} engines: {node: '>= 10'} @@ -5822,6 +5858,13 @@ packages: os: [linux] libc: [musl] + '@next/swc-linux-x64-musl@16.1.7': + resolution: {integrity: sha512-HguhaGwsGr1YAGs68uRKc4aGWxLET+NevJskOcCAwXbwj0fYX0RgZW2gsOCzr9S11CSQPIkxmoSbuVaBp4Z3dA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + '@next/swc-linux-x64-musl@16.2.2': resolution: {integrity: sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==} engines: {node: '>= 10'} @@ -5847,6 +5890,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@16.1.7': + resolution: {integrity: sha512-S0n3KrDJokKTeFyM/vGGGR8+pCmXYrjNTk2ZozOL1C/JFdfUIL9O1ATaJOl5r2POe56iRChbsszrjMAdWSv7kQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-arm64-msvc@16.2.2': resolution: {integrity: sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==} engines: {node: '>= 10'} @@ -5877,6 +5926,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@16.1.7': + resolution: {integrity: sha512-mwgtg8CNZGYm06LeEd+bNnOUfwOyNem/rOiP14Lsz+AnUY92Zq/LXwtebtUiaeVkhbroRCQ0c8GlR4UT1U+0yg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@next/swc-win32-x64-msvc@16.2.2': resolution: {integrity: sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==} engines: {node: '>= 10'} @@ -15656,6 +15711,27 @@ packages: sass: optional: true + next@16.1.7: + resolution: {integrity: sha512-WM0L7WrSvKwoLegLYr6V+mz+RIofqQgVAfHhMp9a88ms0cFX8iX9ew+snpWlSBwpkURJOUdvCEt3uLl3NNzvWg==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + next@16.2.2: resolution: {integrity: sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==} engines: {node: '>=20.9.0'} @@ -17130,11 +17206,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -18204,6 +18275,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -20753,7 +20830,7 @@ snapshots: dependencies: '@babel/compat-data': 7.26.2 '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -22065,11 +22142,6 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.1(jiti@2.6.1))': - dependencies: - eslint: 9.39.1(jiti@2.6.1) - eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} '@eslint-community/regexpp@4.12.2': {} @@ -23451,6 +23523,8 @@ snapshots: '@next/env@16.1.5': {} + '@next/env@16.1.7': {} + '@next/env@16.2.2': {} '@next/eslint-plugin-next@14.2.17': @@ -23478,6 +23552,9 @@ snapshots: '@next/swc-darwin-arm64@16.1.5': optional: true + '@next/swc-darwin-arm64@16.1.7': + optional: true + '@next/swc-darwin-arm64@16.2.2': optional: true @@ -23490,6 +23567,9 @@ snapshots: '@next/swc-darwin-x64@16.1.5': optional: true + '@next/swc-darwin-x64@16.1.7': + optional: true + '@next/swc-darwin-x64@16.2.2': optional: true @@ -23502,6 +23582,9 @@ snapshots: '@next/swc-linux-arm64-gnu@16.1.5': optional: true + '@next/swc-linux-arm64-gnu@16.1.7': + optional: true + '@next/swc-linux-arm64-gnu@16.2.2': optional: true @@ -23514,6 +23597,9 @@ snapshots: '@next/swc-linux-arm64-musl@16.1.5': optional: true + '@next/swc-linux-arm64-musl@16.1.7': + optional: true + '@next/swc-linux-arm64-musl@16.2.2': optional: true @@ -23526,6 +23612,9 @@ snapshots: '@next/swc-linux-x64-gnu@16.1.5': optional: true + '@next/swc-linux-x64-gnu@16.1.7': + optional: true + '@next/swc-linux-x64-gnu@16.2.2': optional: true @@ -23538,6 +23627,9 @@ snapshots: '@next/swc-linux-x64-musl@16.1.5': optional: true + '@next/swc-linux-x64-musl@16.1.7': + optional: true + '@next/swc-linux-x64-musl@16.2.2': optional: true @@ -23550,6 +23642,9 @@ snapshots: '@next/swc-win32-arm64-msvc@16.1.5': optional: true + '@next/swc-win32-arm64-msvc@16.1.7': + optional: true + '@next/swc-win32-arm64-msvc@16.2.2': optional: true @@ -23565,6 +23660,9 @@ snapshots: '@next/swc-win32-x64-msvc@16.1.5': optional: true + '@next/swc-win32-x64-msvc@16.1.7': + optional: true + '@next/swc-win32-x64-msvc@16.2.2': optional: true @@ -28172,6 +28270,33 @@ snapshots: '@sentry/core@10.45.0': {} + '@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.37.0 + '@rollup/plugin-commonjs': 28.0.1(rollup@4.50.1) + '@sentry-internal/browser-utils': 10.11.0 + '@sentry/bundler-plugin-core': 4.3.0(encoding@0.1.13) + '@sentry/core': 10.11.0 + '@sentry/node': 10.11.0 + '@sentry/opentelemetry': 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + '@sentry/react': 10.11.0(react@19.2.3) + '@sentry/vercel-edge': 10.11.0 + '@sentry/webpack-plugin': 4.3.0(encoding@0.1.13)(webpack@5.92.0(esbuild@0.24.2)) + chalk: 3.0.0 + next: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + resolve: 1.22.8 + rollup: 4.50.1 + stacktrace-parser: 0.1.11 + transitivePeerDependencies: + - '@opentelemetry/context-async-hooks' + - '@opentelemetry/core' + - '@opentelemetry/sdk-trace-base' + - encoding + - react + - supports-color + - webpack + '@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.2(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(webpack@5.92.0(esbuild@0.24.2))': dependencies: '@opentelemetry/api': 1.9.0 @@ -28199,34 +28324,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.37.0 - '@rollup/plugin-commonjs': 28.0.1(rollup@4.50.1) - '@sentry-internal/browser-utils': 10.11.0 - '@sentry/bundler-plugin-core': 4.3.0(encoding@0.1.13) - '@sentry/core': 10.11.0 - '@sentry/node': 10.11.0 - '@sentry/opentelemetry': 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) - '@sentry/react': 10.11.0(react@19.2.3) - '@sentry/vercel-edge': 10.11.0 - '@sentry/webpack-plugin': 4.3.0(encoding@0.1.13)(webpack@5.92.0(esbuild@0.24.2)) - chalk: 3.0.0 - next: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - resolve: 1.22.8 - rollup: 4.50.1 - stacktrace-parser: 0.1.11 - transitivePeerDependencies: - - '@opentelemetry/context-async-hooks' - - '@opentelemetry/core' - - '@opentelemetry/sdk-trace-base' - - encoding - - react - - supports-color - - webpack - - '@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))': + '@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.40.0 @@ -28239,7 +28337,7 @@ snapshots: '@sentry/react': 10.45.0(react@19.2.3) '@sentry/vercel-edge': 10.45.0 '@sentry/webpack-plugin': 5.1.1(encoding@0.1.13)(webpack@5.92.0(esbuild@0.24.2)) - next: 16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rollup: 4.57.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -30267,7 +30365,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.2 + '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.46.3 '@typescript-eslint/type-utils': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) @@ -30277,7 +30375,7 @@ snapshots: graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -30337,8 +30435,8 @@ snapshots: '@typescript-eslint/project-service@8.46.3(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3) + '@typescript-eslint/types': 8.46.3 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -30383,7 +30481,7 @@ snapshots: '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -30432,7 +30530,7 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -30454,7 +30552,7 @@ snapshots: '@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.46.3 '@typescript-eslint/types': 8.46.3 '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) @@ -30504,11 +30602,11 @@ snapshots: jose: 5.6.3 neverthrow: 7.2.0 - '@vercel/analytics@1.3.1(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + '@vercel/analytics@1.3.1(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 '@vercel/functions@2.0.0(@aws-sdk/credential-provider-web-identity@3.972.27)': @@ -30546,9 +30644,9 @@ snapshots: xdg-app-paths: 5.1.0 zod: 3.24.4 - '@vercel/speed-insights@1.0.12(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + '@vercel/speed-insights@1.0.12(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': optionalDependencies: - next: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 '@vitejs/plugin-react@4.3.3(vite@7.3.1(@types/node@20.17.6)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.19.3)(yaml@2.6.0))': @@ -31176,7 +31274,7 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 - resolve: 1.22.10 + resolve: 1.22.11 bail@2.0.2: {} @@ -33227,7 +33325,7 @@ snapshots: eslint: 8.57.1 eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) - fast-glob: 3.3.2 + fast-glob: 3.3.3 get-tsconfig: 4.8.1 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -33875,7 +33973,7 @@ snapshots: router: 2.2.0 send: 1.2.0 serve-static: 2.2.0 - statuses: 2.0.2 + statuses: 2.0.1 type-is: 2.0.1 vary: 1.1.2 transitivePeerDependencies: @@ -33933,7 +34031,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-glob@3.3.3: dependencies: @@ -34051,7 +34149,7 @@ snapshots: escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.2 + statuses: 2.0.1 transitivePeerDependencies: - supports-color @@ -34442,9 +34540,9 @@ snapshots: transitivePeerDependencies: - supports-color - geist@1.3.0(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): + geist@1.3.0(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): dependencies: - next: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) generate-function@2.3.1: dependencies: @@ -34646,7 +34744,7 @@ snapshots: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -36913,9 +37011,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-themes@0.2.1(next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next-themes@0.2.1(next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - next: 16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -37083,6 +37181,56 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@16.1.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 16.1.7 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.16 + caniuse-lite: 1.0.30001751 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.7 + '@next/swc-darwin-x64': 16.1.7 + '@next/swc-linux-arm64-gnu': 16.1.7 + '@next/swc-linux-arm64-musl': 16.1.7 + '@next/swc-linux-x64-gnu': 16.1.7 + '@next/swc-linux-x64-musl': 16.1.7 + '@next/swc-win32-arm64-msvc': 16.1.7 + '@next/swc-win32-x64-msvc': 16.1.7 + '@opentelemetry/api': 1.9.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@16.1.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 16.1.7 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.16 + caniuse-lite: 1.0.30001751 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.7 + '@next/swc-darwin-x64': 16.1.7 + '@next/swc-linux-arm64-gnu': 16.1.7 + '@next/swc-linux-arm64-musl': 16.1.7 + '@next/swc-linux-x64-gnu': 16.1.7 + '@next/swc-linux-x64-musl': 16.1.7 + '@next/swc-win32-arm64-msvc': 16.1.7 + '@next/swc-win32-x64-msvc': 16.1.7 + '@opentelemetry/api': 1.9.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@next/env': 16.2.2 @@ -37133,31 +37281,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.2(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@next/env': 16.2.2 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.16 - caniuse-lite: 1.0.30001751 - postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.3) - optionalDependencies: - '@next/swc-darwin-arm64': 16.2.2 - '@next/swc-darwin-x64': 16.2.2 - '@next/swc-linux-arm64-gnu': 16.2.2 - '@next/swc-linux-arm64-musl': 16.2.2 - '@next/swc-linux-x64-gnu': 16.2.2 - '@next/swc-linux-x64-musl': 16.2.2 - '@next/swc-win32-arm64-msvc': 16.2.2 - '@next/swc-win32-x64-msvc': 16.2.2 - '@opentelemetry/api': 1.9.0 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@16.2.2(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@next/env': 16.2.2 @@ -37183,31 +37306,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.2(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@next/env': 16.2.2 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.16 - caniuse-lite: 1.0.30001751 - postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.3) - optionalDependencies: - '@next/swc-darwin-arm64': 16.2.2 - '@next/swc-darwin-x64': 16.2.2 - '@next/swc-linux-arm64-gnu': 16.2.2 - '@next/swc-linux-arm64-musl': 16.2.2 - '@next/swc-linux-x64-gnu': 16.2.2 - '@next/swc-linux-x64-musl': 16.2.2 - '@next/swc-win32-arm64-msvc': 16.2.2 - '@next/swc-win32-x64-msvc': 16.2.2 - '@opentelemetry/api': 1.9.0 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - nf3@0.1.12: {} nice-try@1.0.5: {} @@ -37329,7 +37427,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.10 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -37882,7 +37980,7 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.11 postcss-js@4.0.1(postcss@8.5.6): dependencies: @@ -39071,12 +39169,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -39503,7 +39595,7 @@ snapshots: ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 - statuses: 2.0.2 + statuses: 2.0.1 transitivePeerDependencies: - supports-color @@ -40638,6 +40730,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 diff --git a/sdks/implementations/swift/package.json b/sdks/implementations/swift/package.json index 2e9365239..4ffc57001 100644 --- a/sdks/implementations/swift/package.json +++ b/sdks/implementations/swift/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/swift-sdk", - "version": "2.8.83", + "version": "2.8.84", "private": true, "description": "Stack Auth Swift SDK", "scripts": { diff --git a/sdks/spec/package.json b/sdks/spec/package.json index 5de403fca..89ba1b780 100644 --- a/sdks/spec/package.json +++ b/sdks/spec/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/sdk-spec", - "version": "2.8.83", + "version": "2.8.84", "private": true, "description": "Stack Auth SDK specification files", "scripts": {}