diff --git a/.dockerignore b/.dockerignore index aad8fbb16c3..6b5bbfdcab9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,7 @@ Dockerfile **/node_modules npm-debug.log README.md -.next +**/.next .git .github .turbo diff --git a/apps/builder/.env.local.example b/.env.dev.example similarity index 85% rename from apps/builder/.env.local.example rename to .env.dev.example index d86bc0dd558..cb029f9bcfa 100644 --- a/apps/builder/.env.local.example +++ b/.env.dev.example @@ -1,6 +1,8 @@ -DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot # Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration) ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S + +DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot + NEXTAUTH_URL=http://localhost:3000 NEXT_PUBLIC_VIEWER_URL=http://localhost:3001 @@ -14,5 +16,4 @@ S3_PORT=9000 S3_ENDPOINT=localhost S3_SSL=false -# For more configuration options check out: -# https://docs.typebot.io/self-hosting/configuration \ No newline at end of file +# For more configuration options check out: https://docs.typebot.io/self-hosting/configuration \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000000..2fdb4c724ed --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration) +ENCRYPTION_SECRET=do+UspMmB/rewbX2K/rskFmtgGSSZ8Ta + +DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot + +NEXTAUTH_URL= +NEXT_PUBLIC_VIEWER_URL= + +ADMIN_EMAIL= +# For more configuration options check out: https://docs.typebot.io/self-hosting/configuration \ No newline at end of file diff --git a/.gitignore b/.gitignore index ed076309e11..0236cd30b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules .next -.env .env.local workspace.code-workspace .DS_Store @@ -34,4 +33,6 @@ __env.js typebotsToFix.json **/scripts/logs -snapshots \ No newline at end of file +snapshots + +.env \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e299dc81f4..d42d77d183f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,11 +36,7 @@ These apps are built with awesome web technologies including [Typescript](https: 2. Set up environment variables - Copy [`apps/builder/.env.local.example`](apps/builder/.env.local.example) to `apps/builder/.env.local` - - Copy [`apps/viewer/.env.local.example`](apps/viewer/.env.local.example) to `apps/viewer/.env.local` - - Copy [`packages/prisma/.env.example` ](packages/prisma/.env.example)to `packages/prisma/.env` + Copy [`.env.dev.example`](./.env.dev.example) to `.env` Check out the [Configuration guide](https://docs.typebot.io/self-hosting/configuration) if you want to enable more options diff --git a/Dockerfile b/Dockerfile index 86108905949..20e313f8ff8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,8 @@ RUN pnpm install COPY --from=pruner /app/out/full/ . COPY turbo.json turbo.json -RUN pnpm turbo run build:docker --filter=${SCOPE}... +ENV ENCRYPTION_SECRET=encryption_secret_placeholder123 DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot NEXTAUTH_URL=http://localhost:3000 NEXT_PUBLIC_VIEWER_URL=http://localhost:3001 +RUN pnpm turbo run build --filter=${SCOPE}... FROM base AS runner WORKDIR /app @@ -32,16 +33,15 @@ RUN apt-get -qy update \ && apt-get autoremove -yq \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -COPY ./packages/prisma ./packages/prisma -COPY ./apps/${SCOPE}/.env.docker ./apps/${SCOPE}/.env.production -COPY --from=builder /app/node_modules ./node_modules +COPY ./packages/prisma/postgresql ./packages/prisma/postgresql COPY --from=builder /app/apps/${SCOPE}/public ./apps/${SCOPE}/public COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/standalone ./ COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/static ./apps/${SCOPE}/.next/static +RUN pnpm install next-runtime-env prisma +RUN pnpm prisma generate --schema=packages/prisma/postgresql/schema.prisma; -COPY scripts/inject-runtime-env.sh scripts/${SCOPE}-entrypoint.sh ./ -RUN chmod +x ./${SCOPE}-entrypoint.sh \ - && chmod +x ./inject-runtime-env.sh +COPY scripts/${SCOPE}-entrypoint.sh ./ +RUN chmod +x ./${SCOPE}-entrypoint.sh ENTRYPOINT ./${SCOPE}-entrypoint.sh EXPOSE 3000 diff --git a/README.md b/README.md index 0dd094be7eb..06d8c98671d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Typebot is an open-source chatbot builder. It allows you to create advanced chat Github Stars Commits per month - + License diff --git a/apps/builder/.env.docker b/apps/builder/.env.docker deleted file mode 100644 index fd9e2c3906c..00000000000 --- a/apps/builder/.env.docker +++ /dev/null @@ -1,13 +0,0 @@ -# Don't edit this file -NEXT_PUBLIC_VIEWER_URL= -NEXT_PUBLIC_SMTP_FROM= -NEXT_PUBLIC_GOOGLE_API_KEY= -NEXT_PUBLIC_GIPHY_API_KEY= -NEXT_PUBLIC_STRIPE_PUBLIC_KEY= -NEXT_PUBLIC_SENTRY_DSN= -NEXT_PUBLIC_VIEWER_INTERNAL_URL= -NEXT_PUBLIC_E2E_TEST= -NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME= -NEXT_PUBLIC_UNSPLASH_APP_NAME= -NEXT_PUBLIC_UNSPLASH_ACCESS_KEY= -NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID= diff --git a/apps/builder/.eslintignore b/apps/builder/.eslintignore deleted file mode 100644 index 4b831b41542..00000000000 --- a/apps/builder/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -next.config.js \ No newline at end of file diff --git a/apps/builder/next.config.js b/apps/builder/next.config.mjs similarity index 64% rename from apps/builder/next.config.js rename to apps/builder/next.config.mjs index 230f3b10174..a71cd725cad 100644 --- a/apps/builder/next.config.js +++ b/apps/builder/next.config.mjs @@ -1,6 +1,14 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { withSentryConfig } = require('@sentry/nextjs') -const path = require('path') +import { withSentryConfig } from '@sentry/nextjs' +import { join, dirname } from 'path' +import '@typebot.io/env/dist/env.mjs' +import { configureRuntimeEnv } from 'next-runtime-env/build/configure.js' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = dirname(__filename) + +configureRuntimeEnv() /** @type {import('next').NextConfig} */ const nextConfig = { @@ -10,13 +18,14 @@ const nextConfig = { '@typebot.io/lib', '@typebot.io/schemas', '@typebot.io/emails', + '@typebot.io/env', ], i18n: { defaultLocale: 'en', locales: ['en', 'fr', 'pt', 'de'], }, experimental: { - outputFileTracingRoot: path.join(__dirname, '../../'), + outputFileTracingRoot: join(__dirname, '../../'), }, headers: async () => { return [ @@ -38,7 +47,7 @@ const sentryWebpackPluginOptions = { release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder', } -module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN +export default process.env.NEXT_PUBLIC_SENTRY_DSN ? withSentryConfig( { ...nextConfig, diff --git a/apps/builder/package.json b/apps/builder/package.json index e0e3205bb6a..6de7bc022e2 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -3,13 +3,11 @@ "version": "0.1.0", "license": "AGPL-3.0-or-later", "scripts": { - "dev": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next dev -p 3000", - "build": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next build", - "build:docker": "next build", - "build:env": "cd ../.. && cross-env ENVSH_ENV=./apps/builder/.env.docker ENVSH_OUTPUT=./apps/builder/public/__env.js bash scripts/inject-runtime-env.sh", - "start": "next start", - "lint": "next lint", - "test": "pnpm playwright test", + "dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3000", + "build": "dotenv -e ./.env -e ../../.env -- next build", + "start": "dotenv -e ./.env -e ../../.env -- next start", + "lint": "dotenv -e ./.env -e ../../.env -- next lint", + "test": "dotenv -e ./.env -e ../../.env -- pnpm playwright test", "test:show-report": "pnpm playwright show-report src/test/reporters" }, "dependencies": { @@ -30,6 +28,7 @@ "@paralleldrive/cuid2": "2.2.1", "@sentry/nextjs": "7.58.1", "@stripe/stripe-js": "1.54.1", + "@t3-oss/env-nextjs": "^0.6.0", "@tanstack/react-query": "^4.29.19", "@tanstack/react-table": "8.9.3", "@trpc/client": "10.34.0", @@ -37,6 +36,7 @@ "@trpc/react-query": "10.34.0", "@trpc/server": "10.34.0", "@typebot.io/emails": "workspace:*", + "@typebot.io/env": "workspace:*", "@typebot.io/nextjs": "workspace:*", "@udecode/plate-basic-marks": "21.1.5", "@udecode/plate-common": "^21.1.5", @@ -81,6 +81,7 @@ "qs": "6.11.2", "react": "18.2.0", "react-dom": "18.2.0", + "sharp": "^0.32.4", "slate": "0.94.1", "slate-history": "0.93.0", "slate-hyperscript": "0.77.0", @@ -112,7 +113,8 @@ "@types/qs": "6.9.7", "@types/react": "18.2.15", "@types/tinycolor2": "1.4.3", - "dotenv": "16.3.1", + "dotenv-cli": "^7.2.1", + "next-runtime-env": "^1.6.2", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", "superjson": "^1.12.4", diff --git a/apps/builder/sentry.client.config.js b/apps/builder/sentry.client.config.js index 8c0c540e06b..fad585c2a4f 100644 --- a/apps/builder/sentry.client.config.js +++ b/apps/builder/sentry.client.config.js @@ -1,9 +1,7 @@ import * as Sentry from '@sentry/nextjs' -const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN - Sentry.init({ - dsn: SENTRY_DSN, + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'ResizeObserver loop completed with undelivered notifications.', diff --git a/apps/builder/sentry.server.config.js b/apps/builder/sentry.server.config.js index c7e31494cf6..05de5446c0d 100644 --- a/apps/builder/sentry.server.config.js +++ b/apps/builder/sentry.server.config.js @@ -1,8 +1,6 @@ import * as Sentry from '@sentry/nextjs' -const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN - Sentry.init({ - dsn: SENTRY_DSN, + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder', }) diff --git a/apps/builder/src/components/ImageUploadContent/GiphyPicker.tsx b/apps/builder/src/components/ImageUploadContent/GiphyPicker.tsx index 8ed9043af59..afdcf79fab4 100644 --- a/apps/builder/src/components/ImageUploadContent/GiphyPicker.tsx +++ b/apps/builder/src/components/ImageUploadContent/GiphyPicker.tsx @@ -3,14 +3,14 @@ import { GiphyFetch } from '@giphy/js-fetch-api' import { Grid } from '@giphy/react-components' import { GiphyLogo } from '../logos/GiphyLogo' import React, { useState } from 'react' -import { env, isEmpty } from '@typebot.io/lib' import { TextInput } from '../inputs' +import { env } from '@typebot.io/env' type GiphySearchFormProps = { onSubmit: (url: string) => void } -const giphyFetch = new GiphyFetch(env('GIPHY_API_KEY') as string) +const giphyFetch = new GiphyFetch(env.NEXT_PUBLIC_GIPHY_API_KEY ?? '') export const GiphyPicker = ({ onSubmit }: GiphySearchFormProps) => { const [inputValue, setInputValue] = useState('') @@ -21,7 +21,7 @@ export const GiphyPicker = ({ onSubmit }: GiphySearchFormProps) => { const fetchGifsTrending = (offset: number) => giphyFetch.trending({ offset, limit: 10 }) - return isEmpty(env('GIPHY_API_KEY')) ? ( + return !env.NEXT_PUBLIC_GIPHY_API_KEY ? ( NEXT_PUBLIC_GIPHY_API_KEY is missing in environment ) : ( diff --git a/apps/builder/src/components/ImageUploadContent/UnsplashPicker.tsx b/apps/builder/src/components/ImageUploadContent/UnsplashPicker.tsx index e4c3b724710..5721769205f 100644 --- a/apps/builder/src/components/ImageUploadContent/UnsplashPicker.tsx +++ b/apps/builder/src/components/ImageUploadContent/UnsplashPicker.tsx @@ -14,16 +14,17 @@ import { Text, useColorModeValue, } from '@chakra-ui/react' -import { env, isDefined, isEmpty } from '@typebot.io/lib' +import { isDefined } from '@typebot.io/lib' import { useCallback, useEffect, useRef, useState } from 'react' import { createApi } from 'unsplash-js' import { Basic as UnsplashImage } from 'unsplash-js/dist/methods/photos/types' import { TextInput } from '../inputs' import { UnsplashLogo } from '../logos/UnsplashLogo' import { TextLink } from '../TextLink' +import { env } from '@typebot.io/env' const api = createApi({ - accessKey: env('UNSPLASH_ACCESS_KEY') ?? '', + accessKey: env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY ?? '', }) type Props = { @@ -124,7 +125,7 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => { searchRandomImages() }, []) - if (isEmpty(env('UNSPLASH_ACCESS_KEY'))) + if (!env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY) return ( NEXT_PUBLIC_UNSPLASH_ACCESS_KEY is missing in environment ) @@ -143,9 +144,7 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => { /> @@ -224,9 +223,7 @@ const UnsplashImage = ({ image, onClick }: UnsplashImageProps) => { diff --git a/apps/builder/src/components/inputs/AutocompleteInput.tsx b/apps/builder/src/components/inputs/AutocompleteInput.tsx index 45eb5014dbd..228350a64d5 100644 --- a/apps/builder/src/components/inputs/AutocompleteInput.tsx +++ b/apps/builder/src/components/inputs/AutocompleteInput.tsx @@ -13,7 +13,7 @@ import { } from '@chakra-ui/react' import { useState, useRef, useEffect, ReactNode } from 'react' import { useDebouncedCallback } from 'use-debounce' -import { env, isDefined } from '@typebot.io/lib' +import { isDefined } from '@typebot.io/lib' import { useOutsideClick } from '@/hooks/useOutsideClick' import { useParentModal } from '@/features/graph/providers/ParentModalProvider' import { VariablesButton } from '@/features/variables/components/VariablesButton' @@ -21,6 +21,7 @@ import { Variable } from '@typebot.io/schemas' import { injectVariableInText } from '@/features/variables/helpers/injectVariableInTextInput' import { focusInput } from '@/helpers/focusInput' import { MoreInfoTooltip } from '../MoreInfoTooltip' +import { env } from '@typebot.io/env' type Props = { items: string[] @@ -57,7 +58,7 @@ export const AutocompleteInput = ({ const onChange = useDebouncedCallback( _onChange, - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) useEffect(() => { diff --git a/apps/builder/src/components/inputs/CodeEditor.tsx b/apps/builder/src/components/inputs/CodeEditor.tsx index 8a8480d7d24..a569d07030b 100644 --- a/apps/builder/src/components/inputs/CodeEditor.tsx +++ b/apps/builder/src/components/inputs/CodeEditor.tsx @@ -9,7 +9,7 @@ import { useEffect, useRef, useState } from 'react' import { useDebouncedCallback } from 'use-debounce' import { VariablesButton } from '@/features/variables/components/VariablesButton' import { Variable } from '@typebot.io/schemas' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror' import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night' import { githubLight } from '@uiw/codemirror-theme-github' @@ -53,7 +53,7 @@ export const CodeEditor = ({ _setValue(value) onChange && onChange(value) }, - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) const handleVariableSelected = (variable?: Pick) => { diff --git a/apps/builder/src/components/inputs/NumberInput.tsx b/apps/builder/src/components/inputs/NumberInput.tsx index 54270ad904a..ac4730da0ab 100644 --- a/apps/builder/src/components/inputs/NumberInput.tsx +++ b/apps/builder/src/components/inputs/NumberInput.tsx @@ -14,7 +14,7 @@ import { import { Variable, VariableString } from '@typebot.io/schemas' import { useEffect, useState } from 'react' import { useDebouncedCallback } from 'use-debounce' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import { MoreInfoTooltip } from '../MoreInfoTooltip' type Value = HasVariable extends true | undefined @@ -47,7 +47,7 @@ export const NumberInput = ({ const onValueChangeDebounced = useDebouncedCallback( onValueChange, - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) useEffect( diff --git a/apps/builder/src/components/inputs/TextInput.tsx b/apps/builder/src/components/inputs/TextInput.tsx index ecfbd1a8775..1d5f32b32dc 100644 --- a/apps/builder/src/components/inputs/TextInput.tsx +++ b/apps/builder/src/components/inputs/TextInput.tsx @@ -19,7 +19,7 @@ import React, { useState, } from 'react' import { useDebouncedCallback } from 'use-debounce' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import { MoreInfoTooltip } from '../MoreInfoTooltip' export type TextInputProps = { @@ -69,7 +69,7 @@ export const TextInput = forwardRef(function TextInput( const onChange = useDebouncedCallback( // eslint-disable-next-line @typescript-eslint/no-empty-function _onChange ?? (() => {}), - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) useEffect(() => { diff --git a/apps/builder/src/components/inputs/Textarea.tsx b/apps/builder/src/components/inputs/Textarea.tsx index 9948f8a4c62..59f620b9ad3 100644 --- a/apps/builder/src/components/inputs/Textarea.tsx +++ b/apps/builder/src/components/inputs/Textarea.tsx @@ -11,7 +11,7 @@ import { import { Variable } from '@typebot.io/schemas' import React, { useEffect, useRef, useState } from 'react' import { useDebouncedCallback } from 'use-debounce' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import { MoreInfoTooltip } from '../MoreInfoTooltip' type Props = { @@ -46,7 +46,7 @@ export const Textarea = ({ ) const onChange = useDebouncedCallback( _onChange, - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) useEffect(() => { diff --git a/apps/builder/src/features/account/UserProvider.tsx b/apps/builder/src/features/account/UserProvider.tsx index 60f9d822dd6..5ec0113236b 100644 --- a/apps/builder/src/features/account/UserProvider.tsx +++ b/apps/builder/src/features/account/UserProvider.tsx @@ -1,12 +1,13 @@ import { useSession } from 'next-auth/react' import { useRouter } from 'next/router' import { createContext, ReactNode, useEffect, useState } from 'react' -import { env, isDefined, isNotDefined } from '@typebot.io/lib' +import { isDefined, isNotDefined } from '@typebot.io/lib' import { User } from '@typebot.io/prisma' import { setUser as setSentryUser } from '@sentry/nextjs' import { useToast } from '@/hooks/useToast' import { updateUserQuery } from './queries/updateUserQuery' import { useDebouncedCallback } from 'use-debounce' +import { env } from '@typebot.io/env' export const userContext = createContext<{ user?: User @@ -66,7 +67,7 @@ export const UserProvider = ({ children }: { children: ReactNode }) => { if (error) showToast({ title: error.name, description: error.message }) await refreshUser() }, - env('E2E_TEST') === 'true' ? 0 : debounceTimeout + env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout ) useEffect(() => { diff --git a/apps/builder/src/features/account/account.spec.ts b/apps/builder/src/features/account/account.spec.ts index ffed13c177a..588e81e0fd9 100644 --- a/apps/builder/src/features/account/account.spec.ts +++ b/apps/builder/src/features/account/account.spec.ts @@ -1,5 +1,6 @@ import { getTestAsset } from '@/test/utils/playwright' import test, { expect } from '@playwright/test' +import { env } from '@typebot.io/env' import { userId } from '@typebot.io/lib/playwright/databaseSetup' test.describe.configure({ mode: 'parallel' }) @@ -15,9 +16,9 @@ test('should display user info properly', async ({ page }) => { await expect(page.locator('img >> nth=1')).toHaveAttribute( 'src', new RegExp( - `${process.env.S3_ENDPOINT}${ - process.env.S3_PORT ? `:${process.env.S3_PORT}` : '' - }/${process.env.S3_BUCKET}/public/users/${userId}/avatar`, + `${env.S3_ENDPOINT}${env.S3_PORT ? `:${env.S3_PORT}` : ''}/${ + env.S3_BUCKET + }/public/users/${userId}/avatar`, 'gm' ) ) diff --git a/apps/builder/src/features/account/hooks/useApiTokens.ts b/apps/builder/src/features/account/hooks/useApiTokens.ts index c45d8f6c051..d38aafd373a 100644 --- a/apps/builder/src/features/account/hooks/useApiTokens.ts +++ b/apps/builder/src/features/account/hooks/useApiTokens.ts @@ -1,6 +1,6 @@ import { fetcher } from '@/helpers/fetcher' import useSWR from 'swr' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import { ApiTokenFromServer } from '../types' type ServerResponse = { @@ -18,7 +18,7 @@ export const useApiTokens = ({ userId ? `/api/users/${userId}/api-tokens` : null, fetcher, { - dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined, + dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined, } ) if (error) onError(error) diff --git a/apps/builder/src/features/auth/api/customAdapter.ts b/apps/builder/src/features/auth/api/customAdapter.ts index 1a1265e87e4..828ea8f373c 100644 --- a/apps/builder/src/features/auth/api/customAdapter.ts +++ b/apps/builder/src/features/auth/api/customAdapter.ts @@ -14,6 +14,7 @@ import { convertInvitationsToCollaborations } from '@/features/auth/helpers/conv import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations' import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces' import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan' +import { env } from '@typebot.io/env' export function customAdapter(p: PrismaClient): Adapter { return { @@ -26,8 +27,8 @@ export function customAdapter(p: PrismaClient): Adapter { user.email ) if ( - process.env.DISABLE_SIGNUP === 'true' && - process.env.ADMIN_EMAIL !== user.email && + env.DISABLE_SIGNUP && + env.ADMIN_EMAIL !== user.email && invitations.length === 0 && workspaceInvitations.length === 0 ) diff --git a/apps/builder/src/features/auth/components/OnboardingPage.tsx b/apps/builder/src/features/auth/components/OnboardingPage.tsx index 6640d6c55ab..1f0a02c090a 100644 --- a/apps/builder/src/features/auth/components/OnboardingPage.tsx +++ b/apps/builder/src/features/auth/components/OnboardingPage.tsx @@ -13,8 +13,8 @@ import { useRouter } from 'next/router' import { useEffect, useRef, useState } from 'react' import confetti from 'canvas-confetti' import { useUser } from '@/features/account/hooks/useUser' -import { env, isEmpty } from '@typebot.io/lib' import { useI18n } from '@/locales' +import { env } from '@typebot.io/env' const totalSteps = 5 @@ -37,7 +37,7 @@ export const OnboardingPage = () => { useEffect(() => { if (!user?.createdAt) return - if (isNewUser === false || isEmpty(env('ONBOARDING_TYPEBOT_ID'))) + if (isNewUser === false || !env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID) replace('/typebots') }, [isNewUser, replace, user?.createdAt]) @@ -90,7 +90,7 @@ export const OnboardingPage = () => { { diff --git a/apps/builder/src/features/auth/helpers/getAuthenticatedUser.ts b/apps/builder/src/features/auth/helpers/getAuthenticatedUser.ts index 269fc0e896f..99e48c70433 100644 --- a/apps/builder/src/features/auth/helpers/getAuthenticatedUser.ts +++ b/apps/builder/src/features/auth/helpers/getAuthenticatedUser.ts @@ -5,7 +5,7 @@ import { User } from '@typebot.io/prisma' import { NextApiRequest, NextApiResponse } from 'next' import { getServerSession } from 'next-auth' import { mockedUser } from '../mockedUser' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' export const getAuthenticatedUser = async ( req: NextApiRequest, @@ -13,12 +13,11 @@ export const getAuthenticatedUser = async ( ): Promise => { const bearerToken = extractBearerToken(req) if (bearerToken) return authenticateByToken(bearerToken) - const user = - env('E2E_TEST') === 'true' - ? mockedUser - : ((await getServerSession(req, res, authOptions))?.user as - | User - | undefined) + const user = env.NEXT_PUBLIC_E2E_TEST + ? mockedUser + : ((await getServerSession(req, res, authOptions))?.user as + | User + | undefined) if (!user || !('id' in user)) return setUser({ id: user.id }) return user diff --git a/apps/builder/src/features/billing/api/createCheckoutSession.ts b/apps/builder/src/features/billing/api/createCheckoutSession.ts index ce5a21eb35d..de94a44aa70 100644 --- a/apps/builder/src/features/billing/api/createCheckoutSession.ts +++ b/apps/builder/src/features/billing/api/createCheckoutSession.ts @@ -6,6 +6,7 @@ import Stripe from 'stripe' import { z } from 'zod' import { parseSubscriptionItems } from '../helpers/parseSubscriptionItems' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' +import { env } from '@typebot.io/env' export const createCheckoutSession = authenticatedProcedure .meta({ @@ -57,7 +58,7 @@ export const createCheckoutSession = authenticatedProcedure }, ctx: { user }, }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Stripe environment variables are missing', @@ -82,14 +83,13 @@ export const createCheckoutSession = authenticatedProcedure code: 'NOT_FOUND', message: 'Workspace not found', }) - if (workspace.stripeId) throw new TRPCError({ code: 'BAD_REQUEST', message: 'Customer already exists, use updateSubscription endpoint.', }) - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) diff --git a/apps/builder/src/features/billing/api/createCustomCheckoutSession.ts b/apps/builder/src/features/billing/api/createCustomCheckoutSession.ts index 89d042b6f8b..44987097749 100644 --- a/apps/builder/src/features/billing/api/createCustomCheckoutSession.ts +++ b/apps/builder/src/features/billing/api/createCustomCheckoutSession.ts @@ -5,6 +5,7 @@ import { Plan } from '@typebot.io/prisma' import Stripe from 'stripe' import { z } from 'zod' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' +import { env } from '@typebot.io/env' export const createCustomCheckoutSession = authenticatedProcedure .meta({ @@ -31,7 +32,7 @@ export const createCustomCheckoutSession = authenticatedProcedure ) .mutation( async ({ input: { email, workspaceId, returnUrl }, ctx: { user } }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Stripe environment variables are missing', @@ -61,7 +62,7 @@ export const createCustomCheckoutSession = authenticatedProcedure code: 'NOT_FOUND', message: 'Custom plan not found', }) - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) diff --git a/apps/builder/src/features/billing/api/getBillingPortalUrl.ts b/apps/builder/src/features/billing/api/getBillingPortalUrl.ts index 16f08ef6688..b79b7a4d572 100644 --- a/apps/builder/src/features/billing/api/getBillingPortalUrl.ts +++ b/apps/builder/src/features/billing/api/getBillingPortalUrl.ts @@ -4,6 +4,7 @@ import { TRPCError } from '@trpc/server' import Stripe from 'stripe' import { z } from 'zod' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' +import { env } from '@typebot.io/env' export const getBillingPortalUrl = authenticatedProcedure .meta({ @@ -26,7 +27,7 @@ export const getBillingPortalUrl = authenticatedProcedure }) ) .query(async ({ input: { workspaceId }, ctx: { user } }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'STRIPE_SECRET_KEY var is missing', @@ -50,12 +51,12 @@ export const getBillingPortalUrl = authenticatedProcedure code: 'NOT_FOUND', message: 'Workspace not found', }) - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) const portalSession = await stripe.billingPortal.sessions.create({ customer: workspace.stripeId, - return_url: `${process.env.NEXTAUTH_URL}/typebots`, + return_url: `${env.NEXTAUTH_URL}/typebots`, }) return { billingPortalUrl: portalSession.url, diff --git a/apps/builder/src/features/billing/api/getSubscription.ts b/apps/builder/src/features/billing/api/getSubscription.ts index 0760937bc02..84e1864d013 100644 --- a/apps/builder/src/features/billing/api/getSubscription.ts +++ b/apps/builder/src/features/billing/api/getSubscription.ts @@ -4,8 +4,9 @@ import { TRPCError } from '@trpc/server' import Stripe from 'stripe' import { z } from 'zod' import { subscriptionSchema } from '@typebot.io/schemas/features/billing/subscription' -import { priceIds } from '@typebot.io/lib/pricing' import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden' +import { priceIds } from '@typebot.io/lib/api/pricing' +import { env } from '@typebot.io/env' export const getSubscription = authenticatedProcedure .meta({ @@ -28,7 +29,7 @@ export const getSubscription = authenticatedProcedure }) ) .query(async ({ input: { workspaceId }, ctx: { user } }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Stripe environment variables are missing', @@ -55,7 +56,7 @@ export const getSubscription = authenticatedProcedure return { subscription: null, } - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) const subscriptions = await stripe.subscriptions.list({ diff --git a/apps/builder/src/features/billing/api/listInvoices.ts b/apps/builder/src/features/billing/api/listInvoices.ts index 33ec979c363..8e9ac082bd6 100644 --- a/apps/builder/src/features/billing/api/listInvoices.ts +++ b/apps/builder/src/features/billing/api/listInvoices.ts @@ -6,6 +6,7 @@ import { isDefined } from '@typebot.io/lib' import { z } from 'zod' import { invoiceSchema } from '@typebot.io/schemas/features/billing/invoice' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' +import { env } from '@typebot.io/env' export const listInvoices = authenticatedProcedure .meta({ @@ -28,7 +29,7 @@ export const listInvoices = authenticatedProcedure }) ) .query(async ({ input: { workspaceId }, ctx: { user } }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'STRIPE_SECRET_KEY var is missing', @@ -52,7 +53,7 @@ export const listInvoices = authenticatedProcedure code: 'NOT_FOUND', message: 'Workspace not found', }) - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) const invoices = await stripe.invoices.list({ diff --git a/apps/builder/src/features/billing/api/updateSubscription.ts b/apps/builder/src/features/billing/api/updateSubscription.ts index 5996305468c..3f8dc575a8d 100644 --- a/apps/builder/src/features/billing/api/updateSubscription.ts +++ b/apps/builder/src/features/billing/api/updateSubscription.ts @@ -7,15 +7,13 @@ import { workspaceSchema } from '@typebot.io/schemas' import Stripe from 'stripe' import { isDefined } from '@typebot.io/lib' import { z } from 'zod' -import { - getChatsLimit, - getStorageLimit, - priceIds, -} from '@typebot.io/lib/pricing' +import { getChatsLimit, getStorageLimit } from '@typebot.io/lib/pricing' import { chatPriceIds, storagePriceIds } from './getSubscription' import { createCheckoutSessionUrl } from './createCheckoutSession' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' import { getUsage } from '@typebot.io/lib/api/getUsage' +import { env } from '@typebot.io/env' +import { priceIds } from '@typebot.io/lib/api/pricing' export const updateSubscription = authenticatedProcedure .meta({ @@ -57,7 +55,7 @@ export const updateSubscription = authenticatedProcedure }, ctx: { user }, }) => { - if (!process.env.STRIPE_SECRET_KEY) + if (!env.STRIPE_SECRET_KEY) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Stripe environment variables are missing', @@ -85,7 +83,7 @@ export const updateSubscription = authenticatedProcedure code: 'NOT_FOUND', message: 'Workspace not found', }) - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2022-11-15', }) const { data } = await stripe.subscriptions.list({ @@ -95,10 +93,9 @@ export const updateSubscription = authenticatedProcedure }) const subscription = data[0] as Stripe.Subscription | undefined const currentPlanItemId = subscription?.items.data.find((item) => - [ - process.env.STRIPE_STARTER_PRODUCT_ID, - process.env.STRIPE_PRO_PRODUCT_ID, - ].includes(item.price.product.toString()) + [env.STRIPE_STARTER_PRODUCT_ID, env.STRIPE_PRO_PRODUCT_ID].includes( + item.price.product.toString() + ) )?.id const currentAdditionalChatsItemId = subscription?.items.data.find( (item) => chatPriceIds.includes(item.price.id) diff --git a/apps/builder/src/features/billing/billing.spec.ts b/apps/builder/src/features/billing/billing.spec.ts index 7dbd5a6f49b..107ef01f975 100644 --- a/apps/builder/src/features/billing/billing.spec.ts +++ b/apps/builder/src/features/billing/billing.spec.ts @@ -12,6 +12,7 @@ import { deleteWorkspaces, injectFakeResults, } from '@typebot.io/lib/playwright/databaseActions' +import { env } from '@typebot.io/env' const usageWorkspaceId = createId() const usageTypebotId = createId() @@ -147,7 +148,7 @@ test('plan changes should work', async ({ page }) => { planChangeWorkspaceId, [ { - price: process.env.STRIPE_STARTER_MONTHLY_PRICE_ID, + price: env.STRIPE_STARTER_MONTHLY_PRICE_ID, quantity: 1, }, ], diff --git a/apps/builder/src/features/billing/helpers/parseSubscriptionItems.ts b/apps/builder/src/features/billing/helpers/parseSubscriptionItems.ts index 6ef05ab1e85..ba8911c9e88 100644 --- a/apps/builder/src/features/billing/helpers/parseSubscriptionItems.ts +++ b/apps/builder/src/features/billing/helpers/parseSubscriptionItems.ts @@ -1,8 +1,5 @@ -import { - getChatsLimit, - getStorageLimit, - priceIds, -} from '@typebot.io/lib/pricing' +import { getChatsLimit, getStorageLimit } from '@typebot.io/lib/pricing' +import { priceIds } from '@typebot.io/lib/api/pricing' export const parseSubscriptionItems = ( plan: 'STARTER' | 'PRO', diff --git a/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts b/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts index c02141356df..9000cd86d79 100644 --- a/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts +++ b/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts @@ -4,6 +4,7 @@ import { parseDefaultGroupWithBlock } from '@typebot.io/lib/playwright/databaseH import { defaultPaymentInputOptions, InputBlockType } from '@typebot.io/schemas' import { createId } from '@paralleldrive/cuid2' import { stripePaymentForm } from '@/test/utils/selectorUtils' +import { env } from '@typebot.io/env' test.describe('Payment input block', () => { test('Can configure Stripe account', async ({ page }) => { @@ -23,21 +24,15 @@ test.describe('Payment input block', () => { await page.getByRole('button', { name: 'Select an account' }).click() await page.click('text=Connect new') await page.fill('[placeholder="Typebot"]', 'My Stripe Account') - await page.fill( - '[placeholder="sk_test_..."]', - process.env.STRIPE_SECRET_KEY ?? '' - ) - await page.fill( - '[placeholder="sk_live_..."]', - process.env.STRIPE_SECRET_KEY ?? '' - ) + await page.fill('[placeholder="sk_test_..."]', env.STRIPE_SECRET_KEY ?? '') + await page.fill('[placeholder="sk_live_..."]', env.STRIPE_SECRET_KEY ?? '') await page.fill( '[placeholder="pk_test_..."]', - process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? '' + env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? '' ) await page.fill( '[placeholder="pk_live_..."]', - process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? '' + env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? '' ) await expect(page.locator('button >> text="Connect"')).toBeEnabled() await page.click('button >> text="Connect"') diff --git a/apps/builder/src/features/blocks/integrations/sendEmail/components/SendEmailSettings.tsx b/apps/builder/src/features/blocks/integrations/sendEmail/components/SendEmailSettings.tsx index 9c001a25751..6082d369bfc 100644 --- a/apps/builder/src/features/blocks/integrations/sendEmail/components/SendEmailSettings.tsx +++ b/apps/builder/src/features/blocks/integrations/sendEmail/components/SendEmailSettings.tsx @@ -10,7 +10,7 @@ import { import { CodeEditor } from '@/components/inputs/CodeEditor' import { SendEmailOptions, Variable } from '@typebot.io/schemas' import React from 'react' -import { env, isNotEmpty } from '@typebot.io/lib' +import { isNotEmpty } from '@typebot.io/lib' import { SmtpConfigModal } from './SmtpConfigModal' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' @@ -18,6 +18,7 @@ import { CredentialsDropdown } from '@/features/credentials/components/Credentia import { TextInput, Textarea } from '@/components/inputs' import { useWorkspace } from '@/features/workspace/WorkspaceProvider' import { MoreInfoTooltip } from '@/components/MoreInfoTooltip' +import { env } from '@typebot.io/env' type Props = { options: SendEmailOptions @@ -117,9 +118,9 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => { currentCredentialsId={options.credentialsId} onCredentialsSelect={handleCredentialsSelect} onCreateNewClick={onOpen} - defaultCredentialLabel={env('SMTP_FROM') - ?.match(/<(.*)>/) - ?.pop()} + defaultCredentialLabel={env.NEXT_PUBLIC_SMTP_FROM?.match( + /<(.*)>/ + )?.pop()} /> )} diff --git a/apps/builder/src/features/blocks/integrations/sendEmail/sendEmail.spec.ts b/apps/builder/src/features/blocks/integrations/sendEmail/sendEmail.spec.ts index 266a0580b67..41513c36997 100644 --- a/apps/builder/src/features/blocks/integrations/sendEmail/sendEmail.spec.ts +++ b/apps/builder/src/features/blocks/integrations/sendEmail/sendEmail.spec.ts @@ -2,17 +2,18 @@ import test, { expect } from '@playwright/test' import { importTypebotInDatabase } from '@typebot.io/lib/playwright/databaseActions' import { createId } from '@paralleldrive/cuid2' import { getTestAsset } from '@/test/utils/playwright' +import { env } from '@typebot.io/env' const typebotId = createId() test.describe('Send email block', () => { test('its configuration should work', async ({ page }) => { if ( - !process.env.SMTP_USERNAME || - !process.env.SMTP_PORT || - !process.env.SMTP_HOST || - !process.env.SMTP_PASSWORD || - !process.env.NEXT_PUBLIC_SMTP_FROM + !env.SMTP_USERNAME || + !env.SMTP_PORT || + !env.SMTP_HOST || + !env.SMTP_PASSWORD || + !env.NEXT_PUBLIC_SMTP_FROM ) throw new Error('SMTP_ env vars are missing') await importTypebotInDatabase( @@ -30,21 +31,18 @@ test.describe('Send email block', () => { await expect(createButton).toBeDisabled() await page.fill( '[placeholder="notifications@provider.com"]', - process.env.SMTP_USERNAME + env.SMTP_USERNAME ) await page.fill('[placeholder="John Smith"]', 'John Smith') - await page.fill('[placeholder="mail.provider.com"]', process.env.SMTP_HOST) - await page.fill( - '[placeholder="user@provider.com"]', - process.env.SMTP_USERNAME - ) - await page.fill('[type="password"]', process.env.SMTP_PASSWORD) - await page.fill('input[role="spinbutton"]', process.env.SMTP_PORT) + await page.fill('[placeholder="mail.provider.com"]', env.SMTP_HOST) + await page.fill('[placeholder="user@provider.com"]', env.SMTP_USERNAME) + await page.fill('[type="password"]', env.SMTP_PASSWORD) + await page.fill('input[role="spinbutton"]', env.SMTP_PORT.toString()) await expect(createButton).toBeEnabled() await createButton.click() await expect( - page.locator(`button >> text=${process.env.SMTP_USERNAME}`) + page.locator(`button >> text=${env.SMTP_USERNAME}`) ).toBeVisible() await page.fill( diff --git a/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts b/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts index e0cd764c4dd..60ed9c0d076 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts @@ -7,6 +7,7 @@ import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/web import { createId } from '@paralleldrive/cuid2' import { getTestAsset } from '@/test/utils/playwright' import { apiToken } from '@typebot.io/lib/playwright/databaseSetup' +import { env } from '@typebot.io/env' test.describe('Builder', () => { test('easy configuration should work', async ({ page }) => { @@ -22,7 +23,7 @@ test.describe('Builder', () => { await page.click('text=Configure...') await page.fill( 'input[placeholder="Paste webhook URL..."]', - `${process.env.NEXTAUTH_URL}/api/mock/webhook-easy-config` + `${env.NEXTAUTH_URL}/api/mock/webhook-easy-config` ) await page.click('text=Test the request') await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText( @@ -45,7 +46,7 @@ test.describe('Builder', () => { await page.click('text=Configure...') await page.fill( 'input[placeholder="Paste webhook URL..."]', - `${process.env.NEXTAUTH_URL}/api/mock/webhook` + `${env.NEXTAUTH_URL}/api/mock/webhook` ) await page.click('text=Advanced configuration') await page.getByRole('button', { name: 'GET' }).click() diff --git a/apps/builder/src/features/collaboration/hooks/useInvitations.ts b/apps/builder/src/features/collaboration/hooks/useInvitations.ts index 37c1540e013..d89cae11341 100644 --- a/apps/builder/src/features/collaboration/hooks/useInvitations.ts +++ b/apps/builder/src/features/collaboration/hooks/useInvitations.ts @@ -1,7 +1,7 @@ import { fetcher } from '@/helpers/fetcher' import { Invitation } from '@typebot.io/prisma' import useSWR from 'swr' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' export const useInvitations = ({ typebotId, @@ -14,7 +14,7 @@ export const useInvitations = ({ typebotId ? `/api/typebots/${typebotId}/invitations` : null, fetcher, { - dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined, + dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined, } ) if (error) onError(error) diff --git a/apps/builder/src/features/dashboard/api/getAppVersionProcedure.ts b/apps/builder/src/features/dashboard/api/getAppVersionProcedure.ts index 2371190cae8..2a8e9b1c6fa 100644 --- a/apps/builder/src/features/dashboard/api/getAppVersionProcedure.ts +++ b/apps/builder/src/features/dashboard/api/getAppVersionProcedure.ts @@ -1,5 +1,6 @@ import { publicProcedure } from '@/helpers/server/trpc' +import { env } from '@typebot.io/env' export const getAppVersionProcedure = publicProcedure.query(async () => { - return { commitSha: process.env.VERCEL_GIT_COMMIT_SHA } + return { commitSha: env.VERCEL_GIT_COMMIT_SHA } }) diff --git a/apps/builder/src/features/folders/hooks/useFolders.ts b/apps/builder/src/features/folders/hooks/useFolders.ts index a19b0958c62..8de976f8536 100644 --- a/apps/builder/src/features/folders/hooks/useFolders.ts +++ b/apps/builder/src/features/folders/hooks/useFolders.ts @@ -2,7 +2,7 @@ import { fetcher } from '@/helpers/fetcher' import { DashboardFolder } from '@typebot.io/prisma' import { stringify } from 'qs' import useSWR from 'swr' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' export const useFolders = ({ parentId, @@ -18,7 +18,7 @@ export const useFolders = ({ workspaceId ? `/api/folders?${params}` : null, fetcher, { - dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined, + dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined, } ) if (error) onError(error) diff --git a/apps/builder/src/features/publish/components/SharePage.tsx b/apps/builder/src/features/publish/components/SharePage.tsx index cd0df7e28f0..47c2e86fa0a 100644 --- a/apps/builder/src/features/publish/components/SharePage.tsx +++ b/apps/builder/src/features/publish/components/SharePage.tsx @@ -13,7 +13,7 @@ import { Text, } from '@chakra-ui/react' import { Plan } from '@typebot.io/prisma' -import { isDefined, getViewerUrl, isNotDefined, env } from '@typebot.io/lib' +import { isDefined, isNotDefined } from '@typebot.io/lib' import { isPublicDomainAvailableQuery } from '../queries/isPublicDomainAvailableQuery' import { EditableUrl } from './EditableUrl' import { integrationsList } from './embeds/EmbedButton' @@ -25,6 +25,7 @@ import { CustomDomainsDropdown } from '@/features/customDomains/components/Custo import { TypebotHeader } from '@/features/editor/components/TypebotHeader' import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId' import { useI18n } from '@/locales' +import { env } from '@typebot.io/env' export const SharePage = () => { const t = useI18n() @@ -97,7 +98,7 @@ export const SharePage = () => { {typebot && ( { )} {isNotDefined(typebot?.customDomain) && - env('VERCEL_VIEWER_PROJECT_NAME') ? ( + env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME ? ( <> {isProPlan(workspace) ? ( diff --git a/apps/builder/src/features/publish/components/embeds/modals/IframeModal/IframeSnippet.tsx b/apps/builder/src/features/publish/components/embeds/modals/IframeModal/IframeSnippet.tsx index bdbb1805b21..a601e15efa7 100644 --- a/apps/builder/src/features/publish/components/embeds/modals/IframeModal/IframeSnippet.tsx +++ b/apps/builder/src/features/publish/components/embeds/modals/IframeModal/IframeSnippet.tsx @@ -1,6 +1,6 @@ import { FlexProps } from '@chakra-ui/react' import { useTypebot } from '@/features/editor/providers/TypebotProvider' -import { env, getViewerUrl } from '@typebot.io/lib' +import { getViewerUrl } from '@typebot.io/lib' import { CodeEditor } from '@/components/inputs/CodeEditor' import prettier from 'prettier/standalone' import parserHtml from 'prettier/parser-html' @@ -13,9 +13,7 @@ type Props = { export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => { const { typebot } = useTypebot() - const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${ - typebot?.publicId - }` + const src = `${getViewerUrl()}/${typebot?.publicId}` const code = prettier.format( ``, { parser: 'html', plugins: [parserHtml] } diff --git a/apps/builder/src/features/publish/components/embeds/modals/NotionModal.tsx b/apps/builder/src/features/publish/components/embeds/modals/NotionModal.tsx index 658ad57c0a8..4eaa289f8fd 100644 --- a/apps/builder/src/features/publish/components/embeds/modals/NotionModal.tsx +++ b/apps/builder/src/features/publish/components/embeds/modals/NotionModal.tsx @@ -18,7 +18,7 @@ import { Text, Stack, } from '@chakra-ui/react' -import { env, getViewerUrl } from '@typebot.io/lib' +import { getViewerUrl } from '@typebot.io/lib' import { ModalProps } from '../EmbedButton' export const NotionModal = ({ @@ -49,16 +49,12 @@ export const NotionModal = ({ diff --git a/apps/builder/src/features/publish/components/embeds/modals/WordpressModal/instructions/WordpressStandardInstructions.tsx b/apps/builder/src/features/publish/components/embeds/modals/WordpressModal/instructions/WordpressStandardInstructions.tsx index b41f335f462..6fa1db45cb5 100644 --- a/apps/builder/src/features/publish/components/embeds/modals/WordpressModal/instructions/WordpressStandardInstructions.tsx +++ b/apps/builder/src/features/publish/components/embeds/modals/WordpressModal/instructions/WordpressStandardInstructions.tsx @@ -12,7 +12,7 @@ import { import { useState } from 'react' import { StandardSettings } from '../../../settings/StandardSettings' import { isCloudProdInstance } from '@/helpers/isCloudProdInstance' -import { env, getViewerUrl } from '@typebot.io/lib' +import { getViewerUrl } from '@typebot.io/lib' type Props = { publicId: string @@ -76,9 +76,7 @@ const parseWordpressShortcode = ({ publicId: string }) => { return `[typebot typebot="${publicId}"${ - isCloudProdInstance - ? '' - : ` host="${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}"` + isCloudProdInstance ? '' : ` host="${getViewerUrl()}"` }${width ? ` width="${width}"` : ''}${height ? ` height="${height}"` : ''}] ` } diff --git a/apps/builder/src/features/publish/components/embeds/snippetParsers/shared.ts b/apps/builder/src/features/publish/components/embeds/snippetParsers/shared.ts index f67fbaae278..d90d284405b 100644 --- a/apps/builder/src/features/publish/components/embeds/snippetParsers/shared.ts +++ b/apps/builder/src/features/publish/components/embeds/snippetParsers/shared.ts @@ -1,7 +1,7 @@ import { BotProps } from '@typebot.io/nextjs' import parserBabel from 'prettier/parser-babel' import prettier from 'prettier/standalone' -import { env, getViewerUrl, isDefined } from '@typebot.io/lib' +import { getViewerUrl, isDefined } from '@typebot.io/lib' import { Typebot } from '@typebot.io/schemas' import { isCloudProdInstance } from '@/helpers/isCloudProdInstance' import packageJson from '../../../../../../../../packages/embeds/js/package.json' @@ -58,7 +58,7 @@ export const parseApiHost = ( customDomain: Typebot['customDomain'] | undefined ) => { if (customDomain) return new URL(`https://${customDomain}`).origin - return env('VIEWER_INTERNAL_URL') ?? getViewerUrl() + return getViewerUrl() } export const parseApiHostValue = ( diff --git a/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts b/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts index 4660559a1a0..34e07b4ea7a 100644 --- a/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts +++ b/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts @@ -4,6 +4,7 @@ import { PostHog } from 'posthog-node' import { TRPCError } from '@trpc/server' import got from 'got' import { authenticatedProcedure } from '@/helpers/server/trpc' +import { env } from '@typebot.io/env' // Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services. export const processTelemetryEvent = authenticatedProcedure @@ -26,17 +27,17 @@ export const processTelemetryEvent = authenticatedProcedure }) ) .query(async ({ input: { events }, ctx: { user } }) => { - if (user.email !== process.env.ADMIN_EMAIL) + if (user.email !== env.ADMIN_EMAIL) throw new TRPCError({ code: 'BAD_REQUEST', message: 'Only app admin can process telemetry events', }) - if (!process.env.POSTHOG_API_KEY) + if (!env.POSTHOG_API_KEY) throw new TRPCError({ code: 'BAD_REQUEST', message: 'Server does not have POSTHOG_API_KEY configured', }) - const client = new PostHog(process.env.POSTHOG_API_KEY, { + const client = new PostHog(env.POSTHOG_API_KEY, { host: 'https://eu.posthog.com', }) @@ -65,11 +66,8 @@ export const processTelemetryEvent = authenticatedProcedure groupKey: event.typebotId, properties: { name: event.data.name }, }) - if ( - event.name === 'User created' && - process.env.USER_CREATED_WEBHOOK_URL - ) { - await got.post(process.env.USER_CREATED_WEBHOOK_URL, { + if (event.name === 'User created' && env.USER_CREATED_WEBHOOK_URL) { + await got.post(env.USER_CREATED_WEBHOOK_URL, { json: { email: event.data.email, name: event.data.name ? event.data.name.split(' ')[0] : undefined, diff --git a/apps/builder/src/features/theme/components/general/FontSelector.tsx b/apps/builder/src/features/theme/components/general/FontSelector.tsx index a991ce6c889..8462804dbad 100644 --- a/apps/builder/src/features/theme/components/general/FontSelector.tsx +++ b/apps/builder/src/features/theme/components/general/FontSelector.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' import { Text, HStack } from '@chakra-ui/react' -import { env, isEmpty } from '@typebot.io/lib' import { AutocompleteInput } from '@/components/inputs/AutocompleteInput' +import { env } from '@typebot.io/env' type FontSelectorProps = { activeFont?: string @@ -20,11 +20,9 @@ export const FontSelector = ({ }, []) const fetchPopularFonts = async () => { - if (isEmpty(env('GOOGLE_API_KEY'))) return [] + if (!env.NEXT_PUBLIC_GOOGLE_API_KEY) return [] const response = await fetch( - `https://www.googleapis.com/webfonts/v1/webfonts?key=${env( - 'GOOGLE_API_KEY' - )}&sort=popularity` + `https://www.googleapis.com/webfonts/v1/webfonts?key=${env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity` ) return (await response.json()).items.map( (item: { family: string }) => item.family diff --git a/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts b/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts index 7cdee98f9e6..c4719cc859c 100644 --- a/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts +++ b/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts @@ -1,4 +1,5 @@ import prisma from '@/lib/prisma' +import { env } from '@typebot.io/env' import { CollaboratorsOnTypebots, User } from '@typebot.io/prisma' import { Typebot } from '@typebot.io/schemas' @@ -9,7 +10,7 @@ export const isReadTypebotForbidden = async ( user: Pick ) => { if ( - process.env.ADMIN_EMAIL === user.email || + env.ADMIN_EMAIL === user.email || typebot.collaborators.find( (collaborator) => collaborator.userId === user.id ) diff --git a/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts b/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts index 442bf3f6b22..f36aacb61a5 100644 --- a/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts +++ b/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts @@ -1,8 +1,9 @@ +import { env } from '@typebot.io/env' import { Plan } from '@typebot.io/prisma' export const parseWorkspaceDefaultPlan = (userEmail: string) => { - if (process.env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED - const defaultPlan = process.env.DEFAULT_WORKSPACE_PLAN as Plan | undefined + if (env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED + const defaultPlan = env.DEFAULT_WORKSPACE_PLAN as Plan | undefined if (defaultPlan && Object.values(Plan).includes(defaultPlan)) return defaultPlan return Plan.FREE diff --git a/apps/builder/src/features/workspace/hooks/useMembers.ts b/apps/builder/src/features/workspace/hooks/useMembers.ts index ddd91906475..dd2c2717919 100644 --- a/apps/builder/src/features/workspace/hooks/useMembers.ts +++ b/apps/builder/src/features/workspace/hooks/useMembers.ts @@ -1,7 +1,7 @@ import { WorkspaceInvitation } from '@typebot.io/prisma' import { fetcher } from '@/helpers/fetcher' import useSWR from 'swr' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' import { Member } from '../types' export const useMembers = ({ workspaceId }: { workspaceId?: string }) => { @@ -9,7 +9,7 @@ export const useMembers = ({ workspaceId }: { workspaceId?: string }) => { { members: Member[]; invitations: WorkspaceInvitation[] }, Error >(workspaceId ? `/api/workspaces/${workspaceId}/members` : null, fetcher, { - dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined, + dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined, }) return { members: data?.members ?? [], diff --git a/apps/builder/src/helpers/databaseRules.ts b/apps/builder/src/helpers/databaseRules.ts index f658e594e86..11e5f327c27 100644 --- a/apps/builder/src/helpers/databaseRules.ts +++ b/apps/builder/src/helpers/databaseRules.ts @@ -7,14 +7,14 @@ import { } from '@typebot.io/prisma' import prisma from '@/lib/prisma' import { NextApiResponse } from 'next' -import { env, isNotEmpty } from '@typebot.io/lib' import { forbidden } from '@typebot.io/lib/api' +import { env } from '@typebot.io/env' export const canWriteTypebots = ( typebotIds: string[] | string, user: Pick ): Prisma.TypebotWhereInput => - isNotEmpty(env('E2E_TEST')) + env.NEXT_PUBLIC_E2E_TEST ? { id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds } } : { id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, @@ -40,7 +40,7 @@ export const canReadTypebots = ( ) => ({ id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, workspace: - user.email === process.env.ADMIN_EMAIL || isNotEmpty(env('E2E_TEST')) + user.email === env.ADMIN_EMAIL || env.NEXT_PUBLIC_E2E_TEST ? undefined : { members: { diff --git a/apps/builder/src/helpers/deleteFilesFromBucket.ts b/apps/builder/src/helpers/deleteFilesFromBucket.ts index 77b7fb8e188..b6983d5ee16 100644 --- a/apps/builder/src/helpers/deleteFilesFromBucket.ts +++ b/apps/builder/src/helpers/deleteFilesFromBucket.ts @@ -1,3 +1,4 @@ +import { env } from '@typebot.io/env' import { Client } from 'minio' export const deleteFilesFromBucket = async ({ @@ -5,32 +6,26 @@ export const deleteFilesFromBucket = async ({ }: { urls: string[] }): Promise => { - if ( - !process.env.S3_ENDPOINT || - !process.env.S3_ACCESS_KEY || - !process.env.S3_SECRET_KEY - ) + if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY) throw new Error( 'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY' ) - const useSSL = - process.env.S3_SSL && process.env.S3_SSL === 'false' ? false : true const minioClient = new Client({ - endPoint: process.env.S3_ENDPOINT, - port: process.env.S3_PORT ? parseInt(process.env.S3_PORT) : undefined, - useSSL, - accessKey: process.env.S3_ACCESS_KEY, - secretKey: process.env.S3_SECRET_KEY, - region: process.env.S3_REGION, + endPoint: env.S3_ENDPOINT, + port: env.S3_PORT, + useSSL: env.S3_SSL, + accessKey: env.S3_ACCESS_KEY, + secretKey: env.S3_SECRET_KEY, + region: env.S3_REGION, }) - const bucket = process.env.S3_BUCKET ?? 'typebot' + const bucket = env.S3_BUCKET ?? 'typebot' return minioClient.removeObjects( bucket, urls - .filter((url) => url.includes(process.env.S3_ENDPOINT as string)) + .filter((url) => url.includes(env.S3_ENDPOINT as string)) .map((url) => url.split(`/${bucket}/`)[1]) ) } diff --git a/apps/builder/src/lib/googleSheets.ts b/apps/builder/src/lib/googleSheets.ts index 3cf64c191bb..580ccce5801 100644 --- a/apps/builder/src/lib/googleSheets.ts +++ b/apps/builder/src/lib/googleSheets.ts @@ -4,11 +4,12 @@ import { GoogleSheetsCredentials } from '@typebot.io/schemas' import { isDefined } from '@typebot.io/lib' import { decrypt, encrypt } from '@typebot.io/lib/api' import prisma from './prisma' +import { env } from '@typebot.io/env' export const oauth2Client = new OAuth2Client( - process.env.GOOGLE_CLIENT_ID, - process.env.GOOGLE_CLIENT_SECRET, - `${process.env.NEXTAUTH_URL}/api/credentials/google-sheets/callback` + env.GOOGLE_CLIENT_ID, + env.GOOGLE_CLIENT_SECRET, + `${env.NEXTAUTH_URL}/api/credentials/google-sheets/callback` ) export const getAuthenticatedGoogleClient = async ( diff --git a/apps/builder/src/lib/prisma.ts b/apps/builder/src/lib/prisma.ts index 13abf46c6e2..d91fcca1e55 100644 --- a/apps/builder/src/lib/prisma.ts +++ b/apps/builder/src/lib/prisma.ts @@ -2,6 +2,7 @@ * Instantiates a single instance PrismaClient and save it on the global object. * @link https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices */ +import { env } from '@typebot.io/env' import { PrismaClient } from '@typebot.io/prisma' const prismaGlobal = global as typeof global & { @@ -11,10 +12,10 @@ const prismaGlobal = global as typeof global & { const prisma: PrismaClient = prismaGlobal.prisma || new PrismaClient({ - log: process.env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'], + log: env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'], }) -if (process.env.NODE_ENV !== 'production') { +if (env.NODE_ENV !== 'production') { prismaGlobal.prisma = prisma } diff --git a/apps/builder/src/lib/trpc.ts b/apps/builder/src/lib/trpc.ts index 9892bdeb6c0..b530719556e 100644 --- a/apps/builder/src/lib/trpc.ts +++ b/apps/builder/src/lib/trpc.ts @@ -2,10 +2,9 @@ import { createTRPCProxyClient, httpBatchLink, loggerLink } from '@trpc/client' import { createTRPCNext } from '@trpc/next' import type { AppRouter } from '../helpers/server/routers/v1/trpcRouter' import superjson from 'superjson' -import { env } from '@typebot.io/lib' +import { env } from '@typebot.io/env' -const getBaseUrl = () => - typeof window !== 'undefined' ? '' : process.env.NEXTAUTH_URL +const getBaseUrl = () => (typeof window !== 'undefined' ? '' : env.NEXTAUTH_URL) export const trpc = createTRPCNext({ config() { @@ -36,5 +35,5 @@ export const trpcVanilla = createTRPCProxyClient({ }) export const defaultQueryOptions = { - refetchOnMount: env('E2E_TEST') === 'true', + refetchOnMount: env.NEXT_PUBLIC_E2E_TEST, } diff --git a/apps/builder/src/pages/_document.tsx b/apps/builder/src/pages/_document.tsx index 947090ac62b..21f232da28e 100644 --- a/apps/builder/src/pages/_document.tsx +++ b/apps/builder/src/pages/_document.tsx @@ -12,7 +12,7 @@ const Document = () => ( /> {/* eslint-disable-next-line @next/next/no-sync-scripts */} -