* chore: inject enviroment at the deployment phase (#2174) * Dockerfile CMD env.sh * env.sh generates env-config.js file * index.html imports env-config.js * front/src/config/index.ts imports REACT_APP_SERVER_BASE_URL * Upgrade Dockerfiles * Add compute pg_database_url for render * fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -66,7 +66,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "PORT=3001 craco start --max-warnings=0",
|
"start": "PORT=3001 craco start --max-warnings=0",
|
||||||
"build": "craco build",
|
"build:inject-runtime-env": "./scripts/inject-runtime-env.sh",
|
||||||
|
"build": "craco build && yarn build:inject-runtime-env",
|
||||||
"test": "craco test",
|
"test": "craco test",
|
||||||
"coverage": "craco test --coverage .",
|
"coverage": "craco test --coverage .",
|
||||||
"lint": "eslint src --max-warnings=0",
|
"lint": "eslint src --max-warnings=0",
|
||||||
|
|||||||
3
front/public/env-config.js
Normal file
3
front/public/env-config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
window._env_ = {
|
||||||
|
// This file should stay empty. It will be overwritten by the build process.
|
||||||
|
}
|
||||||
@ -36,6 +36,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<title>Twenty</title>
|
<title>Twenty</title>
|
||||||
|
<script src="%PUBLIC_URL%/env-config.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
11
front/scripts/inject-runtime-env.sh
Executable file
11
front/scripts/inject-runtime-env.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
echo "Generating env-config.js file from runtime environment variables..."
|
||||||
|
|
||||||
|
BASE_FILENAME="build/env-config.js"
|
||||||
|
mkdir -p build
|
||||||
|
rm -rf "./$BASE_FILENAME"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "window._env_ = {"
|
||||||
|
echo " REACT_APP_SERVER_BASE_URL: \"$REACT_APP_SERVER_BASE_URL\","
|
||||||
|
echo "}"
|
||||||
|
} > "./$BASE_FILENAME"
|
||||||
10
front/src/config/index.ts
Normal file
10
front/src/config/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
_env_?: Record<string, string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const REACT_APP_SERVER_BASE_URL =
|
||||||
|
window._env_?.REACT_APP_SERVER_BASE_URL ||
|
||||||
|
process.env.REACT_APP_SERVER_BASE_URL ||
|
||||||
|
'http://localhost:3000';
|
||||||
@ -6,6 +6,7 @@ import { useRecoilState } from 'recoil';
|
|||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { ActivityTarget } from '~/generated/graphql';
|
import { ActivityTarget } from '~/generated/graphql';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||||
@ -23,7 +24,7 @@ export const useApolloFactory = () => {
|
|||||||
|
|
||||||
const apolloClient = useMemo(() => {
|
const apolloClient = useMemo(() => {
|
||||||
apolloRef.current = new ApolloFactory({
|
apolloRef.current = new ApolloFactory({
|
||||||
uri: `${process.env.REACT_APP_SERVER_BASE_URL}/graphql`,
|
uri: `${REACT_APP_SERVER_BASE_URL}/graphql`,
|
||||||
cache: new InMemoryCache({
|
cache: new InMemoryCache({
|
||||||
typePolicies: {
|
typePolicies: {
|
||||||
Activity: {
|
Activity: {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
useRecoilState,
|
useRecoilState,
|
||||||
} from 'recoil';
|
} from 'recoil';
|
||||||
|
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import {
|
import {
|
||||||
useChallengeMutation,
|
useChallengeMutation,
|
||||||
useCheckUserExistsLazyQuery,
|
useCheckUserExistsLazyQuery,
|
||||||
@ -136,8 +137,7 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {
|
const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {
|
||||||
const authServerUrl =
|
const authServerUrl =
|
||||||
process.env.REACT_APP_SERVER_AUTH_URL ??
|
REACT_APP_SERVER_BASE_URL ?? REACT_APP_SERVER_BASE_URL + '/auth';
|
||||||
process.env.REACT_APP_SERVER_BASE_URL + '/auth';
|
|
||||||
window.location.href =
|
window.location.href =
|
||||||
`${authServerUrl}/google/${
|
`${authServerUrl}/google/${
|
||||||
workspaceInviteHash ? '?inviteHash=' + workspaceInviteHash : ''
|
workspaceInviteHash ? '?inviteHash=' + workspaceInviteHash : ''
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ApolloClient, InMemoryCache } from '@apollo/client';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
|
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ export const ApolloMetadataClientProvider = ({
|
|||||||
const apolloMetadataClient = useMemo(() => {
|
const apolloMetadataClient = useMemo(() => {
|
||||||
if (tokenPair?.accessToken.token) {
|
if (tokenPair?.accessToken.token) {
|
||||||
return new ApolloClient({
|
return new ApolloClient({
|
||||||
uri: `${process.env.REACT_APP_SERVER_BASE_URL}/metadata`,
|
uri: `${REACT_APP_SERVER_BASE_URL}/metadata`,
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${tokenPair.accessToken.token}`,
|
Authorization: `Bearer ${tokenPair.accessToken.token}`,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
export const getImageAbsoluteURIOrBase64 = (imageUrl?: string | null) => {
|
export const getImageAbsoluteURIOrBase64 = (imageUrl?: string | null) => {
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
return null;
|
return null;
|
||||||
@ -12,8 +14,7 @@ export const getImageAbsoluteURIOrBase64 = (imageUrl?: string | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serverFilesUrl =
|
const serverFilesUrl =
|
||||||
process.env.REACT_APP_SERVER_FILES_URL ??
|
REACT_APP_SERVER_BASE_URL ?? REACT_APP_SERVER_BASE_URL + '/files';
|
||||||
process.env.REACT_APP_SERVER_BASE_URL + '/files';
|
|
||||||
|
|
||||||
return `${serverFilesUrl}/${imageUrl}`;
|
return `${serverFilesUrl}/${imageUrl}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
FROM node:18.16.0-alpine as build
|
FROM node:18.16.0-alpine as build
|
||||||
|
|
||||||
|
|
||||||
ARG REACT_APP_SERVER_BASE_URL
|
ARG REACT_APP_SERVER_BASE_URL
|
||||||
ARG REACT_APP_SERVER_AUTH_URL
|
ARG REACT_APP_SERVER_AUTH_URL
|
||||||
ARG REACT_APP_SERVER_FILES_URL
|
ARG REACT_APP_SERVER_FILES_URL
|
||||||
@ -18,10 +17,13 @@ COPY ./infra/prod/front/serve.json ./build
|
|||||||
FROM node:18.16.0-alpine as front
|
FROM node:18.16.0-alpine as front
|
||||||
|
|
||||||
WORKDIR /app/front
|
WORKDIR /app/front
|
||||||
|
|
||||||
COPY --from=build /app/front/build ./build
|
COPY --from=build /app/front/build ./build
|
||||||
|
COPY ./front/scripts/inject-runtime-env.sh /app/front/scripts/inject-runtime-env.sh
|
||||||
|
|
||||||
RUN yarn global add serve
|
RUN yarn global add serve
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||||
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the frontend."
|
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the frontend."
|
||||||
|
|
||||||
CMD ["serve", "build"]
|
CMD ["/bin/sh", "-c", "/app/front/scripts/inject-runtime-env.sh && serve build"]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM node:18.16.0-alpine as server
|
FROM node:18.16.0-alpine as build
|
||||||
|
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
COPY ./server/package.json ./
|
COPY ./server/package.json ./
|
||||||
@ -11,6 +11,12 @@ RUN npx prisma generate
|
|||||||
|
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
|
FROM node:18.16.0-alpine as server
|
||||||
|
|
||||||
|
COPY --from=build /app/server/dist ./dist
|
||||||
|
|
||||||
|
WORKDIR /app/server
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||||
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the backend, ensuring it deploys faster and runs the same way regardless of the deployment environment."
|
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the backend, ensuring it deploys faster and runs the same way regardless of the deployment environment."
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ services:
|
|||||||
name: server
|
name: server
|
||||||
env: docker
|
env: docker
|
||||||
dockerfilePath: ./infra/prod/server/Dockerfile
|
dockerfilePath: ./infra/prod/server/Dockerfile
|
||||||
dockerCommand: "sh -c yarn prisma:migrate && yarn database:setup && node dist/src/main"
|
dockerCommand: "sh -c ./scripts/compute-database-url.sh && yarn prisma:migrate && yarn database:setup && node dist/src/main"
|
||||||
autoDeploy: false
|
autoDeploy: false
|
||||||
envVars:
|
envVars:
|
||||||
- key: FRONT_BASE_URL
|
- key: FRONT_BASE_URL
|
||||||
@ -38,8 +38,6 @@ services:
|
|||||||
name: twenty_postgres
|
name: twenty_postgres
|
||||||
type: pserv
|
type: pserv
|
||||||
property: port
|
property: port
|
||||||
- key: PG_DATABASE_URL
|
|
||||||
value: postgres://twenty:twenty@twenty-postgres:5432/default?connection_limit=1
|
|
||||||
disk:
|
disk:
|
||||||
name: twenty-disk
|
name: twenty-disk
|
||||||
mountPath: /.local-storage
|
mountPath: /.local-storage
|
||||||
|
|||||||
2
server/scripts/compute-database-url.sh
Executable file
2
server/scripts/compute-database-url.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
export PG_DATABASE_URL=postgres://twenty:twenty@$PG_DATABASE_HOST:5432/default
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# src/run-integration.sh
|
# scripts/run-integration.sh
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
source $DIR/set-env-test.sh
|
source $DIR/set-env-test.sh
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# scripts/setenv.sh
|
# scripts/set-env-test.sh
|
||||||
|
|
||||||
# Get script's directory
|
# Get script's directory
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|||||||
Reference in New Issue
Block a user