diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 410bdc2d5a..bb982142c3 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -164,4 +164,6 @@ CMD if [ "${DOCKER_CRON_ENABLED:-1}" = "1" ]; then \ fi; \ (cd packages/database && npm run db:migrate:deploy) && \ (cd packages/database && npm run db:create-saml-database:deploy) && \ + (cd packages/database && npm run db:initial-user-setup:deploy) && \ + exec node apps/web/server.js \ No newline at end of file diff --git a/apps/web/lib/constants.ts b/apps/web/lib/constants.ts index d06fa0340e..614e5be42b 100644 --- a/apps/web/lib/constants.ts +++ b/apps/web/lib/constants.ts @@ -286,3 +286,9 @@ export const SENTRY_DSN = env.SENTRY_DSN; export const PROMETHEUS_ENABLED = env.PROMETHEUS_ENABLED === "1"; export const DISABLE_USER_MANAGEMENT = env.DISABLE_USER_MANAGEMENT === "1"; + +//initial setup variables +export const INITIAL_USER_EMAIL = env.INITIAL_USER_EMAIL; +export const INITIAL_USER_PASSWORD = env.INITIAL_USER_PASSWORD; +export const INITIAL_ORGANIZATION_NAME = env.INITIAL_ORGANIZATION_NAME; +export const INITIAL_PROJECT_NAME = env.INITIAL_PROJECT_NAME; diff --git a/apps/web/lib/env.ts b/apps/web/lib/env.ts index 88c36126ea..1fd33dd124 100644 --- a/apps/web/lib/env.ts +++ b/apps/web/lib/env.ts @@ -1,5 +1,6 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; +import { ZUserEmail, ZUserPassword } from "@formbricks/types/user"; export const env = createEnv({ /* @@ -113,6 +114,10 @@ export const env = createEnv({ PROMETHEUS_EXPORTER_PORT: z.string().optional(), PROMETHEUS_ENABLED: z.enum(["1", "0"]).optional(), DISABLE_USER_MANAGEMENT: z.enum(["1", "0"]).optional(), + INITIAL_USER_EMAIL: ZUserEmail.optional(), + INITIAL_USER_PASSWORD: ZUserPassword.optional(), + INITIAL_ORGANIZATION_NAME: z.string().optional(), + INITIAL_PROJECT_NAME: z.string().optional(), }, /* @@ -212,5 +217,9 @@ export const env = createEnv({ PROMETHEUS_ENABLED: process.env.PROMETHEUS_ENABLED, PROMETHEUS_EXPORTER_PORT: process.env.PROMETHEUS_EXPORTER_PORT, DISABLE_USER_MANAGEMENT: process.env.DISABLE_USER_MANAGEMENT, + INITIAL_USER_EMAIL: process.env.INITIAL_USER_EMAIL, + INITIAL_USER_PASSWORD: process.env.INITIAL_USER_PASSWORD, + INITIAL_ORGANIZATION_NAME: process.env.INITIAL_ORGANIZATION_NAME, + INITIAL_PROJECT_NAME: process.env.INITIAL_PROJECT_NAME, }, }); diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index 6829aee398..9ae8e4cb51 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "Login mit Azure", + "continue_with_azure": "Weiter mit Microsoft", "continue_with_email": "Login mit E-Mail", "continue_with_github": "Login mit GitHub", "continue_with_google": "Login mit Google", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 9e7bf1d779..fa83be87e8 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "Continue with Azure", + "continue_with_azure": "Continue with Microsoft", "continue_with_email": "Continue with Email", "continue_with_github": "Continue with GitHub", "continue_with_google": "Continue with Google", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index a550bade77..beeb99bee8 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "Continuer avec Azure", + "continue_with_azure": "Continuer avec Microsoft", "continue_with_email": "Continuer avec l'e-mail", "continue_with_github": "Continuer avec GitHub", "continue_with_google": "Continuer avec Google", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index 40359df0d6..980ad73d27 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "Continuar com Azure", + "continue_with_azure": "Continuar com Microsoft", "continue_with_email": "Continuar com o Email", "continue_with_github": "Continuar com o GitHub", "continue_with_google": "Continuar com o Google", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index eeff7ae456..4fa90ed5be 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "Continuar com Azure", + "continue_with_azure": "Continuar com Microsoft", "continue_with_email": "Continuar com Email", "continue_with_github": "Continuar com GitHub", "continue_with_google": "Continuar com Google", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index 0c78ecea8b..9c9ff8ff19 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -1,6 +1,6 @@ { "auth": { - "continue_with_azure": "使用 Azure 繼續", + "continue_with_azure": "繼續使用 Microsoft", "continue_with_email": "使用電子郵件繼續", "continue_with_github": "使用 GitHub 繼續", "continue_with_google": "使用 Google 繼續", diff --git a/docs/self-hosting/configuration/automated-setup.mdx b/docs/self-hosting/configuration/automated-setup.mdx new file mode 100644 index 0000000000..94b76bed4c --- /dev/null +++ b/docs/self-hosting/configuration/automated-setup.mdx @@ -0,0 +1,85 @@ +--- +title: "Automated Setup" +description: "Automatically create the first user and organization with environment variables" +icon: "robot" +--- + +# Automated Setup + +When deploying Formbricks in production environments, it's often useful to automate the initial setup process. This guide explains how to use environment variables to automatically create the first user, organization, and project during startup. + +## Overview + +Formbricks supports automatic creation of the initial administrator user on first startup through environment variables. This is particularly useful for: + +- Automating deployments of multiple instances +- Setting up Formbricks in Docker or Kubernetes environments +- Integrating with infrastructure as code tools +- Streamlining CI/CD pipelines + +## Configuration + +To enable automated setup, set the following environment variables before starting Formbricks: + +### Required Variables + +| Variable | Description | Example | +| --- | --- | --- | +| `INITIAL_USER_EMAIL` | The email address for the admin user | `admin@example.com` | +| `INITIAL_USER_PASSWORD` | The password for the admin user | `your-secure-password` | + +### Optional Variables + +| Variable | Description | Default Value | Example | +| --- | --- | --- | --- | +| `INITIAL_ORGANIZATION_NAME` | The name of the initial organization | `My Organization` | `Acme Corp` | +| `INITIAL_PROJECT_NAME` | The name of the initial project | *none* | `Customer Feedback` | + +## How It Works + +When Formbricks starts up, it checks if these environment variables are defined and if the database is empty (no users exist). If both conditions are met, it will: + +1. Create an administrator user with the provided email and password +2. Create an organization with the provided or default name +3. Create a project if `INITIAL_PROJECT_NAME` is provided +4. Set up both development and production environments for the project + +After successful setup, you'll be able to log in with the provided credentials immediately. + +## Example Docker Compose Configuration + +Here's an example of how to configure these variables in a Docker Compose file: + +```yaml +version: "3.3" +services: + formbricks: + image: ghcr.io/formbricks/formbricks:latest + environment: + # Required environment variables + WEBAPP_URL: "https://formbricks.example.com" + NEXTAUTH_URL: "https://formbricks.example.com" + DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" + NEXTAUTH_SECRET: "your-nextauth-secret" + ENCRYPTION_KEY: "your-encryption-key" + CRON_SECRET: "your-cron-secret" + + # Automated setup variables + INITIAL_USER_EMAIL: "admin@example.com" + INITIAL_USER_PASSWORD: "your-secure-password" + INITIAL_ORGANIZATION_NAME: "Acme Corp" + INITIAL_PROJECT_NAME: "Customer Feedback" +``` + +## Security Considerations + +- Use a strong, unique password for the admin user +- Consider removing or changing the environment variables after the initial setup +- In production environments, ensure these variables are securely managed and not exposed in logs or configuration files +- Use Docker secrets or environment variable management systems in container orchestration platforms + +## Limitations + +- The automated setup only works if no users exist in the database +- If the setup process is interrupted, you may need to manually clean up the database before retrying +- The feature cannot be used to create additional users after the first user exists \ No newline at end of file diff --git a/docs/self-hosting/configuration/environment-variables.mdx b/docs/self-hosting/configuration/environment-variables.mdx index 14d2811bdf..88f446745c 100644 --- a/docs/self-hosting/configuration/environment-variables.mdx +++ b/docs/self-hosting/configuration/environment-variables.mdx @@ -67,8 +67,13 @@ These variables are present inside your machine’s docker-compose file. Restart | PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | | | PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 | | DOCKER_CRON_ENABLED | Controls whether cron jobs run in the Docker image. Set to 0 to disable (useful for cluster setups). | optional | 1 | -| SURVEY_URL | Set this to change the domain of the survey. | optional | WEBAPP_URL -| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional | -| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional | -| DISABLE_USER_MANAGEMENT | Set this to hide the user management UI. | optional | +| SURVEY_URL | Set this to change the domain of the survey. | optional | WEBAPP_URL | +| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional | | +| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional | | +| DISABLE_USER_MANAGEMENT | Set this to hide the user management UI. | optional | | +| INITIAL_USER_EMAIL | Set this to create an initial user with the given email. | optional | | +| INITIAL_USER_PASSWORD | Set this to create an initial user with the given password. | optional | | +| INITIAL_USER_ORGANIZATION | Set this to create an initial user with the given organization. | optional | | +| INITIAL_USER_PROJECT | Set this to create an initial user with the given project. | optional | | + Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Github Discussions and we’ll try our best to work out a solution with you. diff --git a/packages/database/package.json b/packages/database/package.json index 9fb3d2a516..b375d8b194 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -12,8 +12,10 @@ "db:migrate:dev": "dotenv -e ../../.env -- sh -c \"pnpm prisma generate && tsx ./src/scripts/apply-migrations.ts\"", "db:create-saml-database:deploy": "env SAML_DATABASE_URL=\"${SAML_DATABASE_URL}\" tsx ./src/scripts/create-saml-database.ts", "db:create-saml-database:dev": "dotenv -e ../../.env -- tsx ./src/scripts/create-saml-database.ts", + "db:initial-user-setup:deploy": "env SAML_DATABASE_URL=\"${SAML_DATABASE_URL}\" tsx ./src/scripts/initial-user-setup.ts", + "db:initial-user-setup:dev": "dotenv -e ../../.env -- tsx ./src/scripts/initial-user-setup.ts", "db:push": "prisma db push --accept-data-loss", - "db:setup": "pnpm db:migrate:dev && pnpm db:create-saml-database:dev", + "db:setup": "pnpm db:migrate:dev && pnpm db:create-saml-database:dev && pnpm db:initial-user-setup:dev", "db:start": "pnpm db:setup", "format": "prisma format", "generate": "prisma generate", @@ -26,6 +28,7 @@ "@formbricks/logger": "workspace:*", "@prisma/client": "6.6.0", "@prisma/extension-accelerate": "1.3.0", + "bcryptjs": "3.0.2", "dotenv-cli": "8.0.0", "zod-openapi": "4.2.4" }, diff --git a/packages/database/src/scripts/initial-user-setup.ts b/packages/database/src/scripts/initial-user-setup.ts new file mode 100644 index 0000000000..13c493a13e --- /dev/null +++ b/packages/database/src/scripts/initial-user-setup.ts @@ -0,0 +1,172 @@ +import { hash } from "bcryptjs"; +import { logger } from "@formbricks/logger"; +import { ZProject } from "../../../types/project"; +import { ZUserEmail, ZUserPassword } from "../../../types/user"; +import { ZOrganization } from "../../zod/organizations"; +import { prisma } from "../client"; + +const { INITIAL_USER_EMAIL, INITIAL_USER_PASSWORD, INITIAL_ORGANIZATION_NAME, INITIAL_PROJECT_NAME } = + process.env; + +export const isFreshInstance = async (): Promise => { + try { + const userCount = await prisma.user.count(); + const organizationCount = await prisma.organization.count(); + return userCount === 0 && organizationCount === 0; + } catch (error) { + logger.error("Error checking if instance is fresh:", error); + return false; + } +}; + +const isValidEmail = (email: string): boolean => { + const parseResult = ZUserEmail.safeParse(email); + return parseResult.success; +}; + +const isValidPassword = (password: string): boolean => { + const parseResult = ZUserPassword.safeParse(password); + return parseResult.success; +}; + +const isValidOrganizationName = (name: string): boolean => { + const parseResult = ZOrganization.pick({ name: true }).safeParse({ name }); + return parseResult.success; +}; + +const isValidProjectName = (name: string): boolean => { + const parseResult = ZProject.pick({ name: true }).safeParse({ name }); + return parseResult.success; +}; + +const validateEnvironmentVariables = (): boolean => { + if (INITIAL_USER_EMAIL && !isValidEmail(INITIAL_USER_EMAIL)) { + logger.error("Invalid email format. Please provide a valid email."); + return false; + } + if (INITIAL_USER_PASSWORD && !isValidPassword(INITIAL_USER_PASSWORD)) { + logger.error("Invalid password format. Please provide a valid password."); + return false; + } + if (INITIAL_ORGANIZATION_NAME && !isValidOrganizationName(INITIAL_ORGANIZATION_NAME)) { + logger.error("Invalid organization name format. Please provide a valid organization name."); + return false; + } + if (INITIAL_PROJECT_NAME && !isValidProjectName(INITIAL_PROJECT_NAME)) { + logger.error("Invalid project name format. Please provide a valid project name."); + return false; + } + return true; +}; + +const createInitialUser = async ( + email: string, + password: string, + organizationName?: string, + projectName?: string +): Promise => { + const hashedPassword = await hash(password, 12); + + if (!organizationName) { + // Create only a user without an organization + await prisma.user.create({ + data: { + name: "Admin", + email: email.toLowerCase(), + password: hashedPassword, + emailVerified: new Date(), + locale: "en-US", + }, + }); + return; + } + + // Create user with organization + await prisma.user.create({ + data: { + name: "Admin", + email: email.toLowerCase(), + password: hashedPassword, + emailVerified: new Date(), + locale: "en-US", + memberships: { + create: { + role: "owner", + accepted: true, + organization: { + create: { + name: organizationName, + billing: { + plan: "free", + limits: { projects: 3, monthly: { responses: 1500, miu: 2000 } }, + stripeCustomerId: null, + periodStart: new Date(), + period: "monthly", + }, + ...(projectName && { + projects: { + create: { + name: projectName, + environments: { + create: [{ type: "development" }, { type: "production" }], + }, + }, + }, + }), + }, + }, + }, + }, + }, + }); +}; + +const initEnvironment = async (): Promise => { + try { + logger.info("Checking if initial environment setup is needed..."); + if (!validateEnvironmentVariables()) { + return false; + } + + if (!INITIAL_USER_EMAIL || !INITIAL_USER_PASSWORD) { + logger.info("No initial user credentials provided. Skipping automated setup."); + return true; + } + + const freshInstance = await isFreshInstance(); + if (!freshInstance) { + logger.info("Not a fresh instance (users or organizations exist). Skipping initial setup."); + return true; + } + + logger.info("Fresh instance detected. Creating initial admin user..."); + await createInitialUser( + INITIAL_USER_EMAIL, + INITIAL_USER_PASSWORD, + INITIAL_ORGANIZATION_NAME, + INITIAL_PROJECT_NAME + ); + + logger.info(` +✅ Successfully created initial admin user${INITIAL_ORGANIZATION_NAME ? " and organization" : ""}: +- Email: ${INITIAL_USER_EMAIL} +${INITIAL_ORGANIZATION_NAME ? `- Organization: ${INITIAL_ORGANIZATION_NAME}` : ""} +${INITIAL_PROJECT_NAME && INITIAL_ORGANIZATION_NAME ? `- Project: ${INITIAL_PROJECT_NAME}` : ""} + +You can now log in with the credentials provided in the environment variables. + `); + + return true; + } catch (error) { + logger.error("Error during initial environment setup:", error); + return false; + } +}; + +initEnvironment() + .then(() => { + process.exit(0); + }) + .catch((error: unknown) => { + logger.error(error, "Error creating SAML database"); + }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23be8429cf..406cbe37df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -669,6 +669,9 @@ importers: '@prisma/extension-accelerate': specifier: 1.3.0 version: 1.3.0(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3)) + bcryptjs: + specifier: 3.0.2 + version: 3.0.2 dotenv-cli: specifier: 8.0.0 version: 8.0.0 @@ -15314,7 +15317,7 @@ snapshots: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3) '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.8.3) eslint-config-prettier: 9.1.0(eslint@8.57.0) - eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) + eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) @@ -16805,7 +16808,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.5(eslint@8.57.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.0) @@ -16830,9 +16833,9 @@ snapshots: eslint-plugin-turbo: 2.5.0(eslint@8.57.0)(turbo@2.5.0) turbo: 2.5.0 - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)): dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) eslint-import-resolver-node@0.3.9: dependencies: @@ -16853,11 +16856,11 @@ snapshots: tinyglobby: 0.2.13 unrs-resolver: 1.6.2 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -16909,7 +16912,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16927,7 +16930,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 diff --git a/turbo.json b/turbo.json index 6039ffe65b..b09828722b 100644 --- a/turbo.json +++ b/turbo.json @@ -199,7 +199,11 @@ "UNKEY_ROOT_KEY", "PROMETHEUS_ENABLED", "PROMETHEUS_EXPORTER_PORT", - "DISABLE_USER_MANAGEMENT" + "DISABLE_USER_MANAGEMENT", + "INITIAL_USER_EMAIL", + "INITIAL_USER_PASSWORD", + "INITIAL_ORGANIZATION_NAME", + "INITIAL_PROJECT_NAME" ], "outputs": ["dist/**", ".next/**"] },