diff --git a/.gitignore b/.gitignore
index f2c741028..41786c94c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,5 @@ firebaseServiceAccount.json
tags
dump.sql
+
+__env.js
diff --git a/Dockerfile b/Dockerfile
index 0f7d4e906..996db1858 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,7 +20,7 @@ COPY --from=pruner /app/out/full/ .
COPY ./apps/${SCOPE}/.env.docker ./apps/${SCOPE}/.env.production
COPY ./apps/${SCOPE}/.env.docker ./apps/${SCOPE}/.env.local
RUN apt-get -qy update && apt-get -qy install openssl
-RUN yarn turbo run build --scope=${SCOPE} --include-dependencies --no-deps
+RUN yarn turbo run build:docker --scope=${SCOPE} --include-dependencies --no-deps
RUN find . -name node_modules | xargs rm -rf
FROM base AS runner
@@ -36,10 +36,10 @@ COPY --from=builder /app/apps/${SCOPE}/.next/static ./.next/static
COPY --from=builder /app/apps/${SCOPE}/.env.docker ./.env.production
RUN apt-get -qy update && apt-get -qy install openssl
-COPY entrypoint.sh ./
+COPY env.sh ./
COPY ${SCOPE}-entrypoint.sh ./
RUN chmod +x ./${SCOPE}-entrypoint.sh
-RUN chmod +x ./entrypoint.sh
+RUN chmod +x ./env.sh
ENTRYPOINT ./${SCOPE}-entrypoint.sh
EXPOSE 3000
diff --git a/apps/builder/.env.docker b/apps/builder/.env.docker
index cc204ce97..343791d83 100644
--- a/apps/builder/.env.docker
+++ b/apps/builder/.env.docker
@@ -1,7 +1,7 @@
# Don't edit this file
-NEXT_PUBLIC_VIEWER_URL=DOCKER_NEXT_PUBLIC_VIEWER_URL
-NEXT_PUBLIC_SMTP_FROM=DOCKER_NEXT_PUBLIC_SMTP_FROM
-NEXT_PUBLIC_GOOGLE_API_KEY=DOCKER_NEXT_PUBLIC_GOOGLE_API_KEY
-NEXT_PUBLIC_GIPHY_API_KEY=DOCKER_NEXT_PUBLIC_GIPHY_API_KEY
-NEXT_PUBLIC_STRIPE_PUBLIC_KEY=DOCKER_NEXT_PUBLIC_STRIPE_PUBLIC_KEY
-NEXT_PUBLIC_SENTRY_DSN=DOCKER_NEXT_PUBLIC_SENTRY_DSN
+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=
diff --git a/apps/builder/components/share/ShareContent.tsx b/apps/builder/components/share/ShareContent.tsx
index 9eeb261fe..709a6ac33 100644
--- a/apps/builder/components/share/ShareContent.tsx
+++ b/apps/builder/components/share/ShareContent.tsx
@@ -20,6 +20,7 @@ import { isDefined, isNotDefined } from 'utils'
import { CustomDomainsDropdown } from './customDomain/CustomDomainsDropdown'
import { EditableUrl } from './EditableUrl'
import { integrationsList } from './integrations/EmbedButton'
+import { env } from 'utils'
export const ShareContent = () => {
const { workspace } = useWorkspace()
@@ -58,9 +59,7 @@ export const ShareContent = () => {
{typebot && (
diff --git a/apps/builder/components/share/codeSnippets/Chat/EmbedCode.tsx b/apps/builder/components/share/codeSnippets/Chat/EmbedCode.tsx
index c2974d967..cca185279 100644
--- a/apps/builder/components/share/codeSnippets/Chat/EmbedCode.tsx
+++ b/apps/builder/components/share/codeSnippets/Chat/EmbedCode.tsx
@@ -5,7 +5,7 @@ import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
@@ -21,9 +21,9 @@ export const ChatEmbedCode = ({
const snippet = prettier.format(
createSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
button,
proactiveMessage,
diff --git a/apps/builder/components/share/codeSnippets/Container/EmbedCode.tsx b/apps/builder/components/share/codeSnippets/Container/EmbedCode.tsx
index d2dd0e094..16d13fb15 100644
--- a/apps/builder/components/share/codeSnippets/Container/EmbedCode.tsx
+++ b/apps/builder/components/share/codeSnippets/Container/EmbedCode.tsx
@@ -5,7 +5,7 @@ import { parseInitContainerCode, typebotJsHtml } from '../params'
import { IframeParams } from 'typebot-js'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
type ContainerEmbedCodeProps = {
widthLabel: string
@@ -23,9 +23,9 @@ export const ContainerEmbedCode = ({
const snippet = prettier.format(
parseSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
diff --git a/apps/builder/components/share/codeSnippets/Iframe/EmbedCode.tsx b/apps/builder/components/share/codeSnippets/Iframe/EmbedCode.tsx
index 8656aca81..f9c0f9620 100644
--- a/apps/builder/components/share/codeSnippets/Iframe/EmbedCode.tsx
+++ b/apps/builder/components/share/codeSnippets/Iframe/EmbedCode.tsx
@@ -1,7 +1,7 @@
import { FlexProps } from '@chakra-ui/react'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
type Props = {
widthLabel: string
@@ -14,9 +14,9 @@ export const IframeEmbedCode = ({
}: Props & FlexProps) => {
const { typebot } = useTypebot()
const src = `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`
const code = ``
diff --git a/apps/builder/components/share/codeSnippets/Popup/EmbedCode.tsx b/apps/builder/components/share/codeSnippets/Popup/EmbedCode.tsx
index bccc88bbd..788cdbea1 100644
--- a/apps/builder/components/share/codeSnippets/Popup/EmbedCode.tsx
+++ b/apps/builder/components/share/codeSnippets/Popup/EmbedCode.tsx
@@ -4,7 +4,7 @@ import { useTypebot } from 'contexts/TypebotContext'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
import { parseInitPopupCode, typebotJsHtml } from '../params'
type PopupEmbedCodeProps = {
@@ -18,9 +18,9 @@ export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
createSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
delay,
}),
diff --git a/apps/builder/components/share/codeSnippets/ReactCode.tsx b/apps/builder/components/share/codeSnippets/ReactCode.tsx
index f4cd355e9..68e8c6711 100644
--- a/apps/builder/components/share/codeSnippets/ReactCode.tsx
+++ b/apps/builder/components/share/codeSnippets/ReactCode.tsx
@@ -10,7 +10,7 @@ import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
@@ -21,9 +21,9 @@ export const StandardReactDiv = ({
const snippet = prettier.format(
parseContainerSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
@@ -73,9 +73,9 @@ export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
parsePopupSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
delay,
}),
@@ -124,9 +124,9 @@ export const ChatReactCode = ({
const snippet = prettier.format(
parseBubbleSnippet({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${typebot?.publicId}`,
button,
proactiveMessage,
diff --git a/apps/builder/components/share/customDomain/CustomDomainModal.tsx b/apps/builder/components/share/customDomain/CustomDomainModal.tsx
index e5ba5b0c7..cf3480260 100644
--- a/apps/builder/components/share/customDomain/CustomDomainModal.tsx
+++ b/apps/builder/components/share/customDomain/CustomDomainModal.tsx
@@ -18,7 +18,7 @@ import {
import { useToast } from 'components/shared/hooks/useToast'
import { useEffect, useRef, useState } from 'react'
import { createCustomDomain } from 'services/user'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
const hostnameRegex =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
@@ -118,9 +118,9 @@ export const CustomDomainModal = ({
Value
- {isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL}
+ {isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')}
diff --git a/apps/builder/components/share/integrations/modals/GtmModal/GtmInstructions.tsx b/apps/builder/components/share/integrations/modals/GtmModal/GtmInstructions.tsx
index 599e238bc..204329a4d 100644
--- a/apps/builder/components/share/integrations/modals/GtmModal/GtmInstructions.tsx
+++ b/apps/builder/components/share/integrations/modals/GtmModal/GtmInstructions.tsx
@@ -11,7 +11,7 @@ import { PopupEmbedSettings } from 'components/share/codeSnippets/Popup/EmbedSet
import { CodeEditor } from 'components/shared/CodeEditor'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
import { ModalProps } from '../../EmbedButton'
type GtmInstructionsProps = {
@@ -41,9 +41,9 @@ const StandardInstructions = ({ publicId }: Pick) => {
const jsCode = parseInitContainerCode({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${publicId}`,
})
const headCode = `${typebotJsHtml}
diff --git a/apps/builder/components/share/integrations/modals/NotionModal.tsx b/apps/builder/components/share/integrations/modals/NotionModal.tsx
index 7a9dd614a..f079544c3 100644
--- a/apps/builder/components/share/integrations/modals/NotionModal.tsx
+++ b/apps/builder/components/share/integrations/modals/NotionModal.tsx
@@ -16,7 +16,7 @@ import {
} from '@chakra-ui/react'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
import { ModalProps } from '../EmbedButton'
export const NotionModal = ({
@@ -46,17 +46,17 @@ export const NotionModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${publicId}`}
/>
diff --git a/apps/builder/components/share/integrations/modals/ShopifyModal/ShopifyInstructions.tsx b/apps/builder/components/share/integrations/modals/ShopifyModal/ShopifyInstructions.tsx
index ea80555ae..59d1bc260 100644
--- a/apps/builder/components/share/integrations/modals/ShopifyModal/ShopifyInstructions.tsx
+++ b/apps/builder/components/share/integrations/modals/ShopifyModal/ShopifyInstructions.tsx
@@ -14,7 +14,7 @@ import { BubbleParams } from 'typebot-js'
import { ModalProps } from '../../EmbedButton'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
@@ -46,9 +46,9 @@ const StandardInstructions = ({ publicId }: Pick) => {
const jsCode = parseInitContainerCode({
url: `${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${publicId}`,
})
const headCode = prettier.format(
diff --git a/apps/builder/components/share/integrations/modals/WordpressModal.tsx b/apps/builder/components/share/integrations/modals/WordpressModal.tsx
index 8a9792829..633779897 100644
--- a/apps/builder/components/share/integrations/modals/WordpressModal.tsx
+++ b/apps/builder/components/share/integrations/modals/WordpressModal.tsx
@@ -17,7 +17,7 @@ import {
import { ExternalLinkIcon } from 'assets/icons'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
import { ModalProps } from '../EmbedButton'
export const WordpressModal = ({
@@ -55,17 +55,17 @@ export const WordpressModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
- isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
- ? process.env.NEXT_PUBLIC_VIEWER_URL
- : process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
+ isEmpty(env('VIEWER_INTERNAL_URL'))
+ ? env('VIEWER_URL')
+ : env('VIEWER_INTERNAL_URL')
}/${publicId}`}
/>
diff --git a/apps/builder/components/shared/CodeEditor.tsx b/apps/builder/components/shared/CodeEditor.tsx
index 0b7744744..de6a36f78 100644
--- a/apps/builder/components/shared/CodeEditor.tsx
+++ b/apps/builder/components/shared/CodeEditor.tsx
@@ -9,7 +9,7 @@ import { useDebouncedCallback } from 'use-debounce'
import { linter } from '@codemirror/lint'
import { VariablesButton } from './buttons/VariablesButton'
import { Variable } from 'models'
-import { isEmpty } from 'utils'
+import { env } from 'utils'
const linterExtension = linter(jsonParseLinter())
@@ -43,7 +43,7 @@ export const CodeEditor = ({
setPlainTextValue(value)
onChange && onChange(value)
},
- isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
+ env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
)
useEffect(
diff --git a/apps/builder/components/shared/Graph/Nodes/BlockNode/SettingsPopoverContent/bodies/SendEmailSettings/SendEmailSettings.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/SettingsPopoverContent/bodies/SendEmailSettings/SendEmailSettings.tsx
index f12b9738f..b5e62f52c 100644
--- a/apps/builder/components/shared/Graph/Nodes/BlockNode/SettingsPopoverContent/bodies/SendEmailSettings/SendEmailSettings.tsx
+++ b/apps/builder/components/shared/Graph/Nodes/BlockNode/SettingsPopoverContent/bodies/SendEmailSettings/SendEmailSettings.tsx
@@ -12,6 +12,7 @@ import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
import { Input, Textarea } from 'components/shared/Textbox'
import { CredentialsType, SendEmailOptions } from 'models'
import React, { useState } from 'react'
+import { env } from 'utils'
import { SmtpConfigModal } from './SmtpConfigModal'
type Props = {
@@ -96,9 +97,9 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
currentCredentialsId={options.credentialsId}
onCredentialsSelect={handleCredentialsSelect}
onCreateNewClick={onOpen}
- defaultCredentialLabel={process.env.NEXT_PUBLIC_SMTP_FROM?.match(
- /\<(.*)\>/
- )?.pop()}
+ defaultCredentialLabel={env('SMTP_FROM')
+ ?.match(/\<(.*)\>/)
+ ?.pop()}
refreshDropdownKey={refreshCredentialsKey}
/>
diff --git a/apps/builder/components/shared/ImageUploadContent/GiphySearch.tsx b/apps/builder/components/shared/ImageUploadContent/GiphySearch.tsx
index fb9c68aa6..20bc17c13 100644
--- a/apps/builder/components/shared/ImageUploadContent/GiphySearch.tsx
+++ b/apps/builder/components/shared/ImageUploadContent/GiphySearch.tsx
@@ -4,14 +4,13 @@ import { Grid, SearchContext } from '@giphy/react-components'
import { GiphyLogo } from 'assets/logos'
import React, { useContext, useState, useEffect } from 'react'
import { useDebounce } from 'use-debounce'
+import { env } from 'utils'
type GiphySearchProps = {
onSubmit: (url: string) => void
}
-const giphyFetch = new GiphyFetch(
- process.env.NEXT_PUBLIC_GIPHY_API_KEY as string
-)
+const giphyFetch = new GiphyFetch(env('GIPHY_API_KEY') as string)
export const GiphySearch = ({ onSubmit }: GiphySearchProps) => {
const { fetchGifs, searchKey, setSearch } = useContext(SearchContext)
diff --git a/apps/builder/components/shared/ImageUploadContent/ImageUploadContent.tsx b/apps/builder/components/shared/ImageUploadContent/ImageUploadContent.tsx
index 12bca1df4..3e2079c78 100644
--- a/apps/builder/components/shared/ImageUploadContent/ImageUploadContent.tsx
+++ b/apps/builder/components/shared/ImageUploadContent/ImageUploadContent.tsx
@@ -16,12 +16,7 @@ import { useTypebot } from 'contexts/TypebotContext'
import { BaseEmoji, emojiIndex } from 'emoji-mart'
import { emojis } from './emojis'
import { Input } from '../Textbox/Input'
-import { isEmpty } from 'utils'
-import getConfig from 'next/config'
-
-const {
- publicRuntimeConfig: { NEXT_PUBLIC_GIPHY_API_KEY },
-} = getConfig()
+import { env, isEmpty } from 'utils'
type Props = {
url?: string
@@ -188,10 +183,10 @@ const EmojiContent = ({
}
const GiphyContent = ({ onNewUrl }: ContentProps) => {
- if (isEmpty(NEXT_PUBLIC_GIPHY_API_KEY))
+ if (isEmpty(env('GIPHY_API_KEY')))
return NEXT_PUBLIC_GIPHY_API_KEY is missing in environment
return (
-
+
)
diff --git a/apps/builder/components/shared/SearchableDropdown.tsx b/apps/builder/components/shared/SearchableDropdown.tsx
index a87eaea8d..c07c14b7c 100644
--- a/apps/builder/components/shared/SearchableDropdown.tsx
+++ b/apps/builder/components/shared/SearchableDropdown.tsx
@@ -13,7 +13,7 @@ import {
import { Variable } from 'models'
import { useState, useRef, useEffect, ChangeEvent } from 'react'
import { useDebouncedCallback } from 'use-debounce'
-import { isEmpty } from 'utils'
+import { env } from 'utils'
import { VariablesButton } from './buttons/VariablesButton'
type Props = {
@@ -38,7 +38,7 @@ export const SearchableDropdown = ({
const debounced = useDebouncedCallback(
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValueChange ? onValueChange : () => {},
- isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
+ env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
)
const [filteredItems, setFilteredItems] = useState([
...items
diff --git a/apps/builder/components/shared/SmartNumberInput.tsx b/apps/builder/components/shared/SmartNumberInput.tsx
index 1c1b3a45a..295b89fdf 100644
--- a/apps/builder/components/shared/SmartNumberInput.tsx
+++ b/apps/builder/components/shared/SmartNumberInput.tsx
@@ -8,7 +8,7 @@ import {
} from '@chakra-ui/react'
import { useEffect, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
-import { isEmpty } from 'utils'
+import { env } from 'utils'
export const SmartNumberInput = ({
value,
@@ -23,7 +23,7 @@ export const SmartNumberInput = ({
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
const debounced = useDebouncedCallback(
onValueChange,
- isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
+ env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
)
useEffect(
diff --git a/apps/builder/components/shared/Textbox/TextBox.tsx b/apps/builder/components/shared/Textbox/TextBox.tsx
index 6d7953dca..715242706 100644
--- a/apps/builder/components/shared/Textbox/TextBox.tsx
+++ b/apps/builder/components/shared/Textbox/TextBox.tsx
@@ -7,7 +7,7 @@ import {
import { Variable } from 'models'
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
-import { isEmpty } from 'utils'
+import { env } from 'utils'
import { VariablesButton } from '../buttons/VariablesButton'
export type TextBoxProps = {
@@ -36,7 +36,7 @@ export const TextBox = ({
(value) => {
onChange(value)
},
- isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
+ env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
)
useEffect(() => {
diff --git a/apps/builder/components/shared/VariableSearchInput.tsx b/apps/builder/components/shared/VariableSearchInput.tsx
index 1d468b15e..8eb9927b1 100644
--- a/apps/builder/components/shared/VariableSearchInput.tsx
+++ b/apps/builder/components/shared/VariableSearchInput.tsx
@@ -17,7 +17,7 @@ import cuid from 'cuid'
import { Variable } from 'models'
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
import { useDebouncedCallback } from 'use-debounce'
-import { byId, isEmpty, isNotDefined } from 'utils'
+import { byId, env, isNotDefined } from 'utils'
type Props = {
initialVariableId?: string
@@ -47,7 +47,7 @@ export const VariableSearchInput = ({
const variable = variables.find((v) => v.name === value)
if (variable) onSelectVariable(variable)
},
- isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
+ env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
)
const [filteredItems, setFilteredItems] = useState(
variables ?? []
diff --git a/apps/builder/components/theme/GeneralSettings/FontSelector/FontSelector.tsx b/apps/builder/components/theme/GeneralSettings/FontSelector/FontSelector.tsx
index b72448e78..c3a724b59 100644
--- a/apps/builder/components/theme/GeneralSettings/FontSelector/FontSelector.tsx
+++ b/apps/builder/components/theme/GeneralSettings/FontSelector/FontSelector.tsx
@@ -1,8 +1,7 @@
import React, { useEffect, useState } from 'react'
import { Text, HStack } from '@chakra-ui/react'
import { SearchableDropdown } from '../../../shared/SearchableDropdown'
-import { isEmpty } from 'utils'
-import getConfig from 'next/config'
+import { env, isEmpty } from 'utils'
type FontSelectorProps = {
activeFont?: string
@@ -21,12 +20,11 @@ export const FontSelector = ({
}, [])
const fetchPopularFonts = async () => {
- const {
- publicRuntimeConfig: { NEXT_PUBLIC_GOOGLE_API_KEY },
- } = getConfig()
- if (isEmpty(NEXT_PUBLIC_GOOGLE_API_KEY)) return []
+ if (isEmpty(env('GOOGLE_API_KEY'))) return []
const response = await fetch(
- `https://www.googleapis.com/webfonts/v1/webfonts?key=${NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity`
+ `https://www.googleapis.com/webfonts/v1/webfonts?key=${env(
+ 'GOOGLE_API_KEY'
+ )}&sort=popularity`
)
return (await response.json()).items.map(
(item: { family: string }) => item.family
diff --git a/apps/builder/contexts/TypebotContext/TypebotContext.tsx b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
index acd83b52b..04b9cbdf9 100644
--- a/apps/builder/contexts/TypebotContext/TypebotContext.tsx
+++ b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
@@ -29,7 +29,7 @@ import {
} from 'services/typebots/typebots'
import { fetcher, preventUserFromRefreshing } from 'services/utils'
import useSWR from 'swr'
-import { isDefined, isEmpty, isNotDefined, omit } from 'utils'
+import { env, isDefined, isNotDefined, omit } from 'utils'
import { GroupsActions, groupsActions } from './actions/groups'
import { blocksAction, BlocksActions } from './actions/blocks'
import { variablesAction, VariablesActions } from './actions/variables'
@@ -395,7 +395,7 @@ export const useFetchedTypebot = ({
},
Error
>(`/api/typebots/${typebotId}`, fetcher, {
- dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? undefined : 0,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
})
if (error) onError(error)
return {
diff --git a/apps/builder/next.config.js b/apps/builder/next.config.js
index cf9a34cde..c7f8021ae 100644
--- a/apps/builder/next.config.js
+++ b/apps/builder/next.config.js
@@ -6,11 +6,6 @@ const moduleExports = {
outputStandalone: true,
},
optimizeFonts: false,
- publicRuntimeConfig: {
- NEXT_PUBLIC_GOOGLE_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
- NEXT_PUBLIC_GIPHY_API_KEY: process.env.NEXT_PUBLIC_GIPHY_API_KEY,
- NEXT_PUBLIC_STRIPE_PUBLIC_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY,
- },
}
const sentryWebpackPluginOptions = {
diff --git a/apps/builder/package.json b/apps/builder/package.json
index 9e39fbb79..0483d9d7e 100644
--- a/apps/builder/package.json
+++ b/apps/builder/package.json
@@ -4,8 +4,9 @@
"license": "AGPL-3.0-or-later",
"scripts": {
"dx": "yarn dev",
- "dev": "next dev -p 3000",
- "build": "next build",
+ "dev": "ENVSH_ENV=.env.local bash ../../env.sh next dev -p 3000",
+ "build": "cp .env.docker .env.production && ENVSH_ENV=.env.production bash ../../env.sh next build",
+ "build:docker": "next build",
"start": "next start",
"lint": "next lint",
"test": "yarn playwright test",
diff --git a/apps/builder/pages/_document.tsx b/apps/builder/pages/_document.tsx
index 8b266839d..486cf9fee 100644
--- a/apps/builder/pages/_document.tsx
+++ b/apps/builder/pages/_document.tsx
@@ -22,6 +22,8 @@ class MyDocument extends Document {
rel="stylesheet"
/>
+ {/* eslint-disable-next-line @next/next/no-sync-scripts */}
+
diff --git a/apps/builder/pages/api/auth/[...nextauth].ts b/apps/builder/pages/api/auth/[...nextauth].ts
index d998798dd..0d8b8d5f0 100644
--- a/apps/builder/pages/api/auth/[...nextauth].ts
+++ b/apps/builder/pages/api/auth/[...nextauth].ts
@@ -11,7 +11,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { withSentry } from '@sentry/nextjs'
import { CustomAdapter } from './adapter'
import { User } from 'db'
-import { isNotEmpty } from 'utils'
+import { env, isNotEmpty } from 'utils'
const providers: Provider[] = []
@@ -23,10 +23,7 @@ if (isNotEmpty(process.env.GITHUB_CLIENT_ID))
})
)
-if (
- isNotEmpty(process.env.NEXT_PUBLIC_SMTP_FROM) &&
- process.env.SMTP_AUTH_DISABLED !== 'true'
-)
+if (isNotEmpty(env('SMTP_FROM')) && process.env.SMTP_AUTH_DISABLED !== 'true')
providers.push(
EmailProvider({
server: {
@@ -38,7 +35,7 @@ if (
},
ignoreTLS: process.env.SMTP_IGNORE_TLS === 'true',
},
- from: process.env.NEXT_PUBLIC_SMTP_FROM,
+ from: env('SMTP_FROM'),
})
)
diff --git a/apps/builder/pages/api/typebots/[typebotId]/invitations.ts b/apps/builder/pages/api/typebots/[typebotId]/invitations.ts
index ae328e225..023b4444d 100644
--- a/apps/builder/pages/api/typebots/[typebotId]/invitations.ts
+++ b/apps/builder/pages/api/typebots/[typebotId]/invitations.ts
@@ -8,8 +8,8 @@ import { sendEmailNotification } from 'services/api/emails'
import { getAuthenticatedUser } from 'services/api/utils'
import {
badRequest,
+ env,
forbidden,
- isEmpty,
methodNotAllowed,
notAuthenticated,
} from 'utils'
@@ -66,7 +66,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await prisma.invitation.create({
data: { email: email.toLowerCase(), type, typebotId },
})
- if (isEmpty(process.env.NEXT_PUBLIC_E2E_TEST))
+ if (env('E2E_TEST') !== 'enabled')
await sendEmailNotification({
to: email,
subject: "You've been invited to collaborate 🤝",
diff --git a/apps/builder/playwright/tests/results.spec.ts b/apps/builder/playwright/tests/results.spec.ts
index aa51d0932..11b2bbfe4 100644
--- a/apps/builder/playwright/tests/results.spec.ts
+++ b/apps/builder/playwright/tests/results.spec.ts
@@ -8,6 +8,7 @@ import path from 'path'
import {
createResults,
createTypebots,
+ freeWorkspaceId,
importTypebotInDatabase,
parseDefaultGroupWithBlock,
} from '../services/database'
@@ -123,7 +124,7 @@ test.describe('Results page', () => {
test("Incomplete results shouldn't be displayed", async ({ page }) => {
await prisma.typebot.update({
where: { id: typebotId },
- data: { workspaceId: 'free' },
+ data: { workspaceId: freeWorkspaceId },
})
await page.goto(`/typebots/${typebotId}/results`)
await page.click('text=Unlock')
diff --git a/apps/builder/playwright/tests/settings.spec.ts b/apps/builder/playwright/tests/settings.spec.ts
index 2707557c3..2cf124e41 100644
--- a/apps/builder/playwright/tests/settings.spec.ts
+++ b/apps/builder/playwright/tests/settings.spec.ts
@@ -2,7 +2,7 @@ import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import { defaultTextInputOptions } from 'models'
import path from 'path'
-import { importTypebotInDatabase } from '../services/database'
+import { freeWorkspaceId, importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Settings page', () => {
@@ -127,7 +127,7 @@ test.describe.parallel('Settings page', () => {
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
- workspaceId: 'free',
+ workspaceId: freeWorkspaceId,
}
)
await page.goto(`/typebots/${typebotId}/settings`)
diff --git a/apps/builder/services/api/dbRules.ts b/apps/builder/services/api/dbRules.ts
index 8c5e96b2f..0a5c818fc 100644
--- a/apps/builder/services/api/dbRules.ts
+++ b/apps/builder/services/api/dbRules.ts
@@ -1,7 +1,7 @@
import { CollaborationType, Plan, Prisma, User, WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { NextApiResponse } from 'next'
-import { forbidden, isNotEmpty } from 'utils'
+import { env, forbidden, isNotEmpty } from 'utils'
const parseWhereFilter = (
typebotIds: string[] | string,
@@ -22,7 +22,7 @@ const parseWhereFilter = (
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
- isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
+ isNotEmpty(env('E2E_TEST'))
? undefined
: {
members: {
diff --git a/apps/builder/services/api/emails.ts b/apps/builder/services/api/emails.ts
index 315fa4f74..55c967eef 100644
--- a/apps/builder/services/api/emails.ts
+++ b/apps/builder/services/api/emails.ts
@@ -1,4 +1,5 @@
import { createTransport } from 'nodemailer'
+import { env } from 'utils'
export const sendEmailNotification = ({
to,
@@ -19,7 +20,7 @@ export const sendEmailNotification = ({
})
return transporter.sendMail({
- from: process.env.NEXT_PUBLIC_SMTP_FROM,
+ from: env('SMTP_FORM'),
to,
subject,
html: content,
diff --git a/apps/builder/services/folders.ts b/apps/builder/services/folders.ts
index cf5498322..c56efdb80 100644
--- a/apps/builder/services/folders.ts
+++ b/apps/builder/services/folders.ts
@@ -2,7 +2,7 @@ import { DashboardFolder } from 'db'
import useSWR from 'swr'
import { fetcher } from './utils'
import { stringify } from 'qs'
-import { isNotEmpty, sendRequest } from 'utils'
+import { env, sendRequest } from 'utils'
export const useFolders = ({
parentId,
@@ -18,9 +18,7 @@ export const useFolders = ({
workspaceId ? `/api/folders?${params}` : null,
fetcher,
{
- dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
- ? 0
- : undefined,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
}
)
if (error) onError(error)
diff --git a/apps/builder/services/integrations.ts b/apps/builder/services/integrations.ts
index ea1f8bc3d..e58670637 100644
--- a/apps/builder/services/integrations.ts
+++ b/apps/builder/services/integrations.ts
@@ -1,4 +1,4 @@
-import { sendRequest } from 'utils'
+import { env, sendRequest } from 'utils'
import { stringify } from 'qs'
import useSWR from 'swr'
import { fetcher } from './utils'
@@ -78,7 +78,9 @@ export const executeWebhook = (
{ blockId }: { blockId: string }
) =>
sendRequest({
- url: `${process.env.NEXT_PUBLIC_VIEWER_URL}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook`,
+ url: `${env(
+ 'VIEWER_URL'
+ )}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook`,
method: 'POST',
body: {
variables,
diff --git a/apps/builder/services/stripe.ts b/apps/builder/services/stripe.ts
index 7c7a08c12..9fbaf5dba 100644
--- a/apps/builder/services/stripe.ts
+++ b/apps/builder/services/stripe.ts
@@ -1,7 +1,6 @@
import { Plan, User } from 'db'
import { loadStripe } from '@stripe/stripe-js/pure'
-import { isDefined, isEmpty, sendRequest } from 'utils'
-import getConfig from 'next/config'
+import { env, isDefined, isEmpty, sendRequest } from 'utils'
type Props = {
user: User
@@ -40,10 +39,7 @@ const redirectToCheckout = async ({
plan,
workspaceId,
}: Omit) => {
- const {
- publicRuntimeConfig: { NEXT_PUBLIC_STRIPE_PUBLIC_KEY },
- } = getConfig()
- if (isEmpty(NEXT_PUBLIC_STRIPE_PUBLIC_KEY))
+ if (isEmpty(env('STRIPE_PUBLIC_KEY')))
throw new Error('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is missing in env')
const { data, error } = await sendRequest<{ sessionId: string }>({
method: 'POST',
@@ -57,7 +53,7 @@ const redirectToCheckout = async ({
},
})
if (error || !data) return
- const stripe = await loadStripe(NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
+ const stripe = await loadStripe(env('STRIPE_PUBLIC_KEY') as string)
await stripe?.redirectToCheckout({
sessionId: data?.sessionId,
})
diff --git a/apps/builder/services/typebots/invitations.ts b/apps/builder/services/typebots/invitations.ts
index a58ddc1da..acc4772cb 100644
--- a/apps/builder/services/typebots/invitations.ts
+++ b/apps/builder/services/typebots/invitations.ts
@@ -1,7 +1,7 @@
import { CollaborationType, Invitation } from 'db'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
-import { isNotEmpty, sendRequest } from 'utils'
+import { env, sendRequest } from 'utils'
export const useInvitations = ({
typebotId,
@@ -14,9 +14,7 @@ export const useInvitations = ({
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
fetcher,
{
- dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
- ? 0
- : undefined,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
}
)
if (error) onError(error)
diff --git a/apps/builder/services/typebots/results.tsx b/apps/builder/services/typebots/results.tsx
index 3c44fae1e..e2f925637 100644
--- a/apps/builder/services/typebots/results.tsx
+++ b/apps/builder/services/typebots/results.tsx
@@ -7,7 +7,7 @@ import {
import useSWRInfinite from 'swr/infinite'
import { stringify } from 'qs'
import { Answer } from 'db'
-import { isDefined, isEmpty, sendRequest } from 'utils'
+import { env, isDefined, sendRequest } from 'utils'
import { fetcher } from 'services/utils'
import { HStack, Text, Wrap, WrapItem } from '@chakra-ui/react'
import { CodeIcon, CalendarIcon, FileIcon } from 'assets/icons'
@@ -54,9 +54,7 @@ export const useResults = ({
fetcher,
{
revalidateAll: true,
- dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
- ? undefined
- : 0,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
}
)
diff --git a/apps/builder/services/typebots/typebots.ts b/apps/builder/services/typebots/typebots.ts
index 400296e3b..a25030a1c 100644
--- a/apps/builder/services/typebots/typebots.ts
+++ b/apps/builder/services/typebots/typebots.ts
@@ -46,13 +46,13 @@ import useSWR from 'swr'
import { fetcher, toKebabCase } from '../utils'
import {
isBubbleBlockType,
- isNotEmpty,
isWebhookBlock,
omit,
blockHasItems,
blockTypeHasItems,
blockTypeHasOption,
blockTypeHasWebhook,
+ env,
} from 'utils'
import { dequal } from 'dequal'
import { stringify } from 'qs'
@@ -83,9 +83,7 @@ export const useTypebots = ({
{ typebots: TypebotInDashboard[] },
Error
>(workspaceId ? `/api/typebots?${params}` : null, fetcher, {
- dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
- ? 0
- : undefined,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
})
if (error) onError(error)
return {
diff --git a/apps/builder/services/user/apiTokens.ts b/apps/builder/services/user/apiTokens.ts
index 84c9d4af3..4283659e6 100644
--- a/apps/builder/services/user/apiTokens.ts
+++ b/apps/builder/services/user/apiTokens.ts
@@ -1,7 +1,7 @@
import { ApiToken } from 'db'
import { fetcher } from 'services/utils'
import useSWR, { KeyedMutator } from 'swr'
-import { isNotEmpty, sendRequest } from 'utils'
+import { env, sendRequest } from 'utils'
export type ApiTokenFromServer = { id: string; name: string; createdAt: string }
@@ -25,9 +25,7 @@ export const useApiTokens = ({
userId ? `/api/users/${userId}/api-tokens` : null,
fetcher,
{
- dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
- ? 0
- : undefined,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
}
)
if (error) onError(error)
diff --git a/apps/builder/services/workspace/member.ts b/apps/builder/services/workspace/member.ts
index 1f8d6fbd7..edc441026 100644
--- a/apps/builder/services/workspace/member.ts
+++ b/apps/builder/services/workspace/member.ts
@@ -1,7 +1,7 @@
import { MemberInWorkspace, WorkspaceInvitation } from 'db'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
-import { isEmpty, sendRequest } from 'utils'
+import { env, sendRequest } from 'utils'
export type Member = MemberInWorkspace & {
name: string | null
@@ -14,7 +14,7 @@ export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
{ members: Member[]; invitations: WorkspaceInvitation[] },
Error
>(workspaceId ? `/api/workspaces/${workspaceId}/members` : null, fetcher, {
- dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? undefined : 0,
+ dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
})
return {
members: data?.members,
diff --git a/apps/viewer/.env.docker b/apps/viewer/.env.docker
index 5641a53f1..22e558f5a 100644
--- a/apps/viewer/.env.docker
+++ b/apps/viewer/.env.docker
@@ -1,2 +1,2 @@
# Don't edit this file
-NEXT_PUBLIC_VIEWER_URL=DOCKER_NEXT_PUBLIC_VIEWER_URL
+NEXT_PUBLIC_VIEWER_URL=
diff --git a/apps/viewer/layouts/ErrorPage.tsx b/apps/viewer/layouts/ErrorPage.tsx
index cc20fc3bd..531170465 100644
--- a/apps/viewer/layouts/ErrorPage.tsx
+++ b/apps/viewer/layouts/ErrorPage.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
export const ErrorPage = ({ error }: { error: Error }) => {
return (
@@ -12,7 +12,7 @@ export const ErrorPage = ({ error }: { error: Error }) => {
flexDirection: 'column',
}}
>
- {isEmpty(process.env.NEXT_PUBLIC_VIEWER_URL) ? (
+ {isEmpty(env('VIEWER_URL')) ? (
<>
NEXT_PUBLIC_VIEWER_URL is missing
diff --git a/apps/viewer/package.json b/apps/viewer/package.json
index 1306eaf81..8a8d37d29 100644
--- a/apps/viewer/package.json
+++ b/apps/viewer/package.json
@@ -4,8 +4,9 @@
"version": "0.1.0",
"scripts": {
"dx": "yarn dev",
- "dev": "next dev -p 3001",
- "build": "next build",
+ "dev": "ENVSH_ENV=.env.local bash ../../env.sh next dev -p 3001",
+ "build": "cp .env.docker .env.production && ENVSH_ENV=.env.production bash ../../env.sh next build",
+ "build:docker": "next build",
"start": "next start",
"lint": "next lint",
"test": "yarn playwright test",
diff --git a/apps/viewer/pages/[[...publicId]].tsx b/apps/viewer/pages/[[...publicId]].tsx
index 4483bede4..37db174ba 100644
--- a/apps/viewer/pages/[[...publicId]].tsx
+++ b/apps/viewer/pages/[[...publicId]].tsx
@@ -3,7 +3,7 @@ import { NotFoundPage } from 'layouts/NotFoundPage'
import { PublicTypebot } from 'models'
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
import sanitizeHtml from 'sanitize-html'
-import { isDefined, isNotDefined, omit } from 'utils'
+import { env, isDefined, isNotDefined, omit } from 'utils'
import { TypebotPage, TypebotPageProps } from '../layouts/TypebotPage'
import prisma from '../libs/prisma'
@@ -16,9 +16,9 @@ export const getServerSideProps: GetServerSideProps = async (
const { host, forwardedHost } = getHost(context.req)
try {
if (!host) return { props: {} }
- const viewerUrls = (process.env.NEXT_PUBLIC_VIEWER_URL ?? '').split(',')
+ const viewerUrls = (env('VIEWER_URL') ?? '').split(',')
const isMatchingViewerUrl =
- process.env.NEXT_PUBLIC_E2E_TEST === 'enabled'
+ env('E2E_TEST') === 'enabled'
? true
: viewerUrls.some(
(url) =>
diff --git a/apps/viewer/pages/_document.tsx b/apps/viewer/pages/_document.tsx
new file mode 100644
index 000000000..01d164b74
--- /dev/null
+++ b/apps/viewer/pages/_document.tsx
@@ -0,0 +1,31 @@
+import Document, {
+ Html,
+ Head,
+ Main,
+ NextScript,
+ DocumentContext,
+} from 'next/document'
+
+class MyDocument extends Document {
+ static async getInitialProps(ctx: DocumentContext) {
+ const initialProps = await Document.getInitialProps(ctx)
+ return { ...initialProps }
+ }
+
+ render() {
+ return (
+
+
+ {/* eslint-disable-next-line @next/next/no-sync-scripts */}
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default MyDocument
diff --git a/apps/viewer/services/api/dbRules.ts b/apps/viewer/services/api/dbRules.ts
index 644d5c828..d21f0fef8 100644
--- a/apps/viewer/services/api/dbRules.ts
+++ b/apps/viewer/services/api/dbRules.ts
@@ -1,5 +1,5 @@
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
-import { isNotEmpty } from 'utils'
+import { env } from 'utils'
const parseWhereFilter = (
typebotIds: string[] | string,
@@ -20,7 +20,7 @@ const parseWhereFilter = (
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
- isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
+ env('E2E_TEST') === 'enabled'
? undefined
: {
members: {
diff --git a/builder-entrypoint.sh b/builder-entrypoint.sh
index cda017990..1dc7bffc2 100644
--- a/builder-entrypoint.sh
+++ b/builder-entrypoint.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-./entrypoint.sh
+./env.sh
./node_modules/.bin/prisma generate;
diff --git a/docker-compose.build.yml b/docker-compose.build.yml
index 395501d10..1bb46f961 100644
--- a/docker-compose.build.yml
+++ b/docker-compose.build.yml
@@ -4,7 +4,7 @@ services:
image: postgres:13
restart: always
volumes:
- - db_data:/var/lib/postgresql/data
+ - build_db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=typebot
- POSTGRES_PASSWORD=typebot
@@ -28,7 +28,6 @@ services:
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
- ADMIN_EMAIL=me@email.com
- NEXTAUTH_URL_INTERNAL=http://host.docker.internal:8080
- - NEXT_PUBLIC_GOOGLE_CLIENT_ID=fnejwkn
typebot-viewer:
build:
context: .
@@ -43,4 +42,4 @@ services:
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
volumes:
- db_data:
+ build_db_data:
diff --git a/env.sh b/env.sh
new file mode 100644
index 000000000..8cb09dddf
--- /dev/null
+++ b/env.sh
@@ -0,0 +1,161 @@
+#!/bin/bash
+# env.sh - Dead-simple .env file reader and generator
+# Tunghsiao Liu (t@sparanoid.com)
+#
+# Inspired by:
+# - https://github.com/andrewmclagan/react-env
+# - https://www.freecodecamp.org/news/7f9d42a91d70/
+# - https://github.com/kunokdev/cra-runtime-environment-variables
+#
+# Features:
+# - Designed to be used for Next.js app inside a Docker container (General
+# React app should also work)
+# - Read env file and generate __env.js file for runtime client use
+# - Merge current environment variables passing to it (Useful for Docker images)
+# - No dependencies (More like a lite version of react-env). This is important
+# to keep our container image as small as possible.
+#
+# Usage:
+# - General usage:
+# $ ./env.sh
+#
+# - Replacing varaible:
+# $ NEXT_PUBLIC_API_BASE=xxx ./env.sh
+#
+# - Enviroment variable not in whitelist will be discarded:
+# $ BAD_ENV=zzz ./env.sh
+#
+# - Change script options:
+# $ ENVSH_ENV="./.env.staging" ENVSH_OUTPUT="./public/config.js" ./env.sh
+#
+# - Use it inside Dockerfile:
+# RUN chmod +x ./env.sh
+# ENTRYPOINT ["./env.sh"]
+#
+# Debug:
+# NEXT_PUBLIC_OB_ENV=123_from_fish NEXT_BAD_ENV=zzz NEXT_PUBLIC_OB_TESTNEW=testenv NEXT_PUBLIC_CODE_UPLOAD_SIZE_LIMIT=6666 ./env.sh
+
+echo -e "env.sh loaded"
+
+# Config
+ENVSH_ENV="${ENVSH_ENV:-"./.env.production"}"
+ENVSH_PREFIX="${ENVSH_PREFIX:-"NEXT_PUBLIC_"}"
+ENVSH_PREFIX_STRIP="${ENVSH_PREFIX_STRIP:-true}"
+
+# Can be `window.__env = {` or `const ENV = {` or whatever you want
+ENVSH_PREPEND="${ENVSH_PREPEND:-"window.__env = {"}"
+ENVSH_APPEND="${ENVSH_APPEND:-"}"}"
+ENVSH_OUTPUT="${ENVSH_OUTPUT:-"./public/__env.js"}"
+
+# Utils
+__green() {
+ printf '\033[1;31;32m%b\033[0m' "$1"
+}
+
+__yellow() {
+ printf '\033[1;31;33m%b\033[0m' "$1"
+}
+
+__red() {
+ printf '\033[1;31;40m%b\033[0m' "$1"
+}
+
+__info() {
+ printf "%s\n" "$1"
+}
+
+__debug() {
+ ENVSH_VERBOSE="${ENVSH_VERBOSE:-"false"}"
+ if [ "$ENVSH_VERBOSE" == "true" ]; then
+ printf "ENVSH_VERBOSE: %s\n" "$1"
+ fi
+}
+
+ENVSH_SED="sed"
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ echo "macOS detected, switching to gsed"
+
+ if command -v gsed >/dev/null 2>&1 ; then
+ __info "$(__green "Found"): $(gsed --version | head -n 1)"
+ else
+ __info "gsed not found, trying to install..."
+
+ if command -v brew >/dev/null 2>&1 ; then
+ __info "$(__green "Found"): $(brew --version | head -n 1)"
+ brew install gnu-sed
+ else
+ __info "$(__red "Homebrew not found, install it first: https://brew.sh/")"
+ exit 1;
+ fi
+
+ fi
+
+ ENVSH_SED="gsed"
+fi
+
+# Recreate config file
+rm -f "$ENVSH_OUTPUT"
+touch "$ENVSH_OUTPUT"
+
+# Create an array from inline variables
+matched_envs=$(env | grep ${ENVSH_PREFIX})
+IFS=$'\n' read -r -d '' -a matched_envs_arr <<< "$matched_envs"
+__info "Matched inline env:"
+for matched_env in "${matched_envs_arr[@]}"; do
+ echo $matched_env
+done
+
+# Add assignment
+echo "$ENVSH_PREPEND" >> "$ENVSH_OUTPUT"
+
+# Check if file exists
+[[ -f "$ENVSH_ENV" ]] || { echo "$ENVSH_ENV does not exist" ; exec "$@" ;}
+
+# Process .env for runtime client use
+__info "$(__green "Reading ${ENVSH_ENV}...")"
+while IFS= read -r line
+do
+ # Check if this line is a valid environment variable and matches our prefix
+ if printf '%s' "$line" | grep -e "=" | grep -e "$ENVSH_PREFIX"; then
+
+ # Read and apply environment variable if exists
+ # NOTE: <<< here operator not working with `sh`
+ awk -F '=' '{print $1 ": \"" (ENVIRON[$1] ? ENVIRON[$1] : $2) "\","}' \
+ <<< "$line" >> "$ENVSH_OUTPUT"
+ fi
+done < "$ENVSH_ENV"
+echo "$ENVSH_APPEND" >> "$ENVSH_OUTPUT"
+
+# Strip prefix if needed
+$ENVSH_PREFIX_STRIP && $ENVSH_SED -i'' -e "s~$ENVSH_PREFIX~~g" "$ENVSH_OUTPUT"
+
+# NOTE: This step is not necessary because variables on pages inside the
+# Next.js prod server won't be changed. They're already inlined during the
+# build time.
+for matched_env in "${matched_envs_arr[@]}"; do
+ echo $matched_env
+done
+
+for i in "${!matched_envs_arr[@]}"; do
+ IFS='=' read -ra key_arr <<< "${matched_envs_arr[$i]}"
+ key=${key_arr[0]}
+
+ if [[ "${matched_envs_arr[$i]}" = *"${key}"* ]]; then
+ index="$i"
+ __info "Got index from inline env: ${index}, replacing ${key}"
+ find "$ENVSH_ENV" -type f -exec $ENVSH_SED -i'' \
+ -e "s~$key=.*~${matched_envs_arr[$index]}~g" {} \;
+ fi
+done
+
+# Print result
+__debug "$(__green "Done! Final result in ${ENVSH_OUTPUT}:")"
+__debug "`cat "$ENVSH_OUTPUT"`"
+
+__debug "$(__green "Done! Modified ${ENVSH_ENV}:")"
+__debug "`cat "$ENVSH_ENV"`"
+
+__info "$(__green "env.sh done\n")"
+
+# Accepting commands (for Docker)
+exec "$@"
diff --git a/package.json b/package.json
index a6c0fe853..82523da63 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"docker:up": "docker compose -f docker-compose.dev.yml up -d",
"docker:nuke": "docker compose -f docker-compose.dev.yml down --volumes --remove-orphans",
"dev:prepare": "turbo run build --scope=bot-engine --no-deps --include-dependencies && turbo run build --scope=typebot-js --no-deps",
- "dev": "yarn docker:up && yarn dev:prepare && turbo run dx --parallel",
+ "dev": "yarn docker:up && yarn dev:prepare && NEXT_PUBLIC_E2E_TEST=false turbo run dx --parallel",
"dev:mocking": "yarn docker:up && NEXT_PUBLIC_E2E_TEST=enabled turbo run dx --parallel",
"build": "yarn docker:up && turbo run build",
"test:builder": "cd apps/builder && yarn test",
diff --git a/packages/bot-engine/src/components/TypebotViewer.tsx b/packages/bot-engine/src/components/TypebotViewer.tsx
index fa201b451..49416c503 100644
--- a/packages/bot-engine/src/components/TypebotViewer.tsx
+++ b/packages/bot-engine/src/components/TypebotViewer.tsx
@@ -21,7 +21,7 @@ import {
} from 'models'
import { Log } from 'db'
import { LiteBadge } from './LiteBadge'
-import { isEmpty } from 'utils'
+import { env, isEmpty } from 'utils'
export type TypebotViewerProps = {
typebot: PublicTypebot
@@ -41,7 +41,7 @@ export type TypebotViewerProps = {
export const TypebotViewer = ({
typebot,
- apiHost = process.env.NEXT_PUBLIC_VIEWER_URL?.split(',')[0],
+ apiHost = env('VIEWER_URL')?.split(',')[0],
isPreview = false,
isLoading = false,
style,
diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts
index e43c1a711..26b0486f4 100644
--- a/packages/utils/src/utils.ts
+++ b/packages/utils/src/utils.ts
@@ -243,3 +243,19 @@ export const uploadFiles = async ({
}
return urls
}
+
+declare const window: any
+
+const isBrowser = () => {
+ return Boolean(typeof window !== 'undefined' && window.__env)
+}
+
+export const env = (key = ''): string | undefined => {
+ if (isBrowser() && window.__env) {
+ return window.__env[key] === "''" ? undefined : window.__env[key]
+ }
+
+ return process.env['NEXT_PUBLIC_' + key] === "''"
+ ? undefined
+ : (process.env['NEXT_PUBLIC_' + key] as string)
+}
diff --git a/turbo.json b/turbo.json
index 681b7d053..ebca1ad80 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,6 +1,10 @@
{
"baseBranch": "origin/main",
"pipeline": {
+ "build:docker": {
+ "dependsOn": ["^build"],
+ "outputs": [".next/**", "dist/**", "build/**", "node_modules/@prisma/**"]
+ },
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**", "build/**", "node_modules/@prisma/**"]
diff --git a/viewer-entrypoint.sh b/viewer-entrypoint.sh
index 747610578..88db0b63f 100644
--- a/viewer-entrypoint.sh
+++ b/viewer-entrypoint.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-./entrypoint.sh
+./env.sh
./node_modules/.bin/prisma generate;