Commit 6655687f authored by tom goriunov's avatar tom goriunov Committed by GitHub

image build process improvements (#1045)

* auto-generate .env.production file

* env placeholders congruity check

* refactor yarn dev and docker tasks

* [skip ci] pass git tag and commit sha to dev app

* [skip ci] clean up
parent c41fef88
...@@ -3,4 +3,5 @@ SENTRY_CSP_REPORT_URI=xxx ...@@ -3,4 +3,5 @@ SENTRY_CSP_REPORT_URI=xxx
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx
\ No newline at end of file NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
\ No newline at end of file
# app config
NEXT_PUBLIC_APP_ENV=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_ENV__
NEXT_PUBLIC_APP_INSTANCE=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_INSTANCE__
NEXT_PUBLIC_APP_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PROTOCOL__
NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__
NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__
NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__
# version config
NEXT_PUBLIC_GIT_COMMIT_SHA=__PLACEHOLDER_FOR_NEXT_PUBLIC_GIT_COMMIT_SHA__
NEXT_PUBLIC_GIT_TAG=__PLACEHOLDER_FOR_NEXT_PUBLIC_GIT_TAG__
# network config
NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__
NEXT_PUBLIC_NETWORK_SHORT_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SHORT_NAME__
NEXT_PUBLIC_NETWORK_LOGO=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_LOGO__
NEXT_PUBLIC_NETWORK_LOGO_DARK=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_LOGO_DARK__
NEXT_PUBLIC_NETWORK_ICON=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ICON__
NEXT_PUBLIC_NETWORK_ICON_DARK=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ICON_DARK__
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME__
NEXT_PUBLIC_NETWORK_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ID__
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_NAME__
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL__
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS__
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS__
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED__
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE__
NEXT_PUBLIC_NETWORK_RPC_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_RPC_URL__
NEXT_PUBLIC_IS_TESTNET=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_TESTNET__
# ui config
NEXT_PUBLIC_FEATURED_NETWORKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FEATURED_NETWORKS__
NEXT_PUBLIC_FOOTER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_LINKS__
NEXT_PUBLIC_NETWORK_EXPLORERS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_EXPLORERS__
NEXT_PUBLIC_OTHER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_OTHER_LINKS__
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_CONFIG_URL__
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM__
NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__
NEXT_PUBLIC_HOMEPAGE_CHARTS=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_CHARTS__
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR__
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND__
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER__
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME__
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP__
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE__
NEXT_PUBLIC_AD_BANNER_PROVIDER=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_BANNER_PROVIDER__
NEXT_PUBLIC_AD_TEXT_PROVIDER=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_TEXT_PROVIDER__
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=__PLACEHOLDER_FOR_NEXT_PUBLIC_GRAPHIQL_TRANSACTION__
NEXT_PUBLIC_WEB3_DEFAULT_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DEFAULT_WALLET__
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET__
NEXT_PUBLIC_HIDE_INDEXING_ALERT=__PLACEHOLDER_FOR_NEXT_PUBLIC_HIDE_INDEXING_ALERT__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__
NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_VISUALIZE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_VISUALIZE_API_HOST__
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_CONTRACT_INFO_API_HOST__
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_ADMIN_SERVICE_API_HOST__
NEXT_PUBLIC_API_SPEC_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_SPEC_URL__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID__
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=__PLACEHOLDER_FOR_NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY__
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID__
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=__PLACEHOLDER_FOR_NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN__
# l2 config
NEXT_PUBLIC_IS_L2_NETWORK=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_L2_NETWORKL__
NEXT_PUBLIC_L1_BASE_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_L1_BASE_URL__
NEXT_PUBLIC_L2_WITHDRAWAL_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_L2_WITHDRAWAL_URL__
# beacon chain config
NEXT_PUBLIC_HAS_BEACON_CHAIN=__PLACEHOLDER_FOR_NEXT_PUBLIC_HAS_BEACON_CHAIN__
NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL__
node_modules node_modules
node_modules_linux node_modules_linux
playwright/envs.js playwright/envs.js
\ No newline at end of file deploy/tools/envs-validator/index.js
\ No newline at end of file
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
# dependencies # dependencies
/node_modules /node_modules
/node_modules_linux /node_modules_linux
/**/node_modules
/.pnp /.pnp
.pnp.js .pnp.js
...@@ -48,9 +47,4 @@ yarn-error.log* ...@@ -48,9 +47,4 @@ yarn-error.log*
/playwright/.browser/ /playwright/.browser/
/playwright/envs.js /playwright/envs.js
**.dec** **.dec**
\ No newline at end of file
# tools: envs-validator
/deploy/tools/envs-validator/index.js
/deploy/tools/envs-validator/envs.ts
/deploy/tools/envs-validator/schema.ts
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// DEV SERVER // DEV SERVER
{ {
"type": "shell", "type": "shell",
"command": "yarn dev:${input:devServerConfig}", "command": "yarn dev:preset ${input:dev_config_preset}",
"problemMatcher": [], "problemMatcher": [],
"label": "dev server", "label": "dev server",
"detail": "start local dev server", "detail": "start local dev server",
...@@ -243,11 +243,11 @@ ...@@ -243,11 +243,11 @@
} }
}, },
{ {
"type": "npm", "type": "shell",
"script": "start:docker:poa_core", "command": "yarn start:docker:preset ${input:dev_config_preset}",
"problemMatcher": [], "problemMatcher": [],
"label": "docker: run poa", "label": "docker: run",
"detail": "run docker container for POA network", "detail": "run docker container with env preset",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "shared", "panel": "shared",
...@@ -311,17 +311,17 @@ ...@@ -311,17 +311,17 @@
}, },
{ {
"type": "pickString", "type": "pickString",
"id": "devServerConfig", "id": "dev_config_preset",
"description": "Choose dev server config:", "description": "Choose dev server config preset:",
"options": [ "options": [
"main", "main",
"main:L2", "main.L2",
"poa_core", "poa_core",
"eth_goerli", "eth_goerli",
"eth", "eth",
"localhost", "localhost",
], ],
"default": "" "default": "main"
}, },
], ],
} }
\ No newline at end of file
...@@ -5,58 +5,72 @@ FROM node:18-alpine AS deps ...@@ -5,58 +5,72 @@ FROM node:18-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
# Install dependencies for App ### APP
# Install dependencies
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN apk add git RUN apk add git
RUN yarn --frozen-lockfile RUN yarn --frozen-lockfile
# Install dependencies for ENVs checker ### ENV VARIABLES CHECKER
# Install dependencies
WORKDIR /envs-validator WORKDIR /envs-validator
COPY ./deploy/tools/envs-validator/package.json ./deploy/tools/envs-validator/yarn.lock ./ COPY ./deploy/tools/envs-validator/package.json ./deploy/tools/envs-validator/yarn.lock ./
RUN yarn --frozen-lockfile RUN yarn --frozen-lockfile
# ***************************** # *****************************
# ****** STAGE 2: Build ******* # ****** STAGE 2: Build *******
# ***************************** # *****************************
FROM node:18-alpine AS builder FROM node:18-alpine AS builder
RUN apk add --no-cache --upgrade libc6-compat bash
# Build app for production # pass commit sha and git tag to the app image
ARG GIT_COMMIT_SHA
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
ARG GIT_TAG
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
ENV NODE_ENV production
### APP
# Copy dependencies and source code
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
COPY .env.template .env.production
RUN rm -rf ./deploy/tools/envs-validator # Generate .env.production with ENVs placeholders and save build args into .env file
COPY --chmod=+x ./deploy/scripts/make_envs_template.sh ./
RUN ./make_envs_template.sh ./docs/ENVS.md
# Next.js collects completely anonymous telemetry data about general usage. # Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry # Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build. # Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1 # ENV NEXT_TELEMETRY_DISABLED 1
# Build app for production
RUN yarn build RUN yarn build
# Build ENVs checker
### ENV VARIABLES CHECKER
# Copy dependencies and source code, then build
WORKDIR /envs-validator WORKDIR /envs-validator
COPY --from=deps /envs-validator/node_modules ./node_modules COPY --from=deps /envs-validator/node_modules ./node_modules
COPY ./deploy/tools/envs-validator . COPY ./deploy/tools/envs-validator .
COPY ./types/envs.ts . COPY ./types/envs.ts .
RUN yarn build RUN yarn build
# ***************************** # *****************************
# ******* STAGE 3: Run ******** # ******* STAGE 3: Run ********
# ***************************** # *****************************
# Production image, copy all the files and run next # Production image, copy all the files and run next
FROM node:18-alpine AS runner FROM node:18-alpine AS runner
WORKDIR /app RUN apk add --no-cache --upgrade bash
# pass commit sha to the app (uses by sentry.io as release version)
ARG GIT_COMMIT_SHA
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
# pass git tag to the app (for the footer link) ### APP
ARG GIT_TAG WORKDIR /app
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime. # Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1 # ENV NEXT_TELEMETRY_DISABLED 1
...@@ -68,20 +82,17 @@ COPY --from=builder /app/public ./public ...@@ -68,20 +82,17 @@ COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/package.json ./package.json
COPY --from=builder /envs-validator/index.js ./envs-validator.js COPY --from=builder /envs-validator/index.js ./envs-validator.js
# Copy scripts and ENV templates file # Copy scripts and ENVs file
COPY ./deploy/scripts/entrypoint.sh . COPY --chmod=+x ./deploy/scripts/entrypoint.sh .
COPY ./deploy/scripts/replace_envs.sh . COPY --chmod=+x ./deploy/scripts/replace_envs.sh .
COPY .env.template . COPY --from=builder /app/.env.production .
COPY --from=builder /app/.env .
# Automatically leverage output traces to reduce image size # Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing # https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN apk add --no-cache --upgrade bash
RUN ["chmod", "+x", "./entrypoint.sh"]
RUN ["chmod", "+x", "./replace_envs.sh"]
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]
USER nextjs USER nextjs
......
...@@ -26,13 +26,14 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH ...@@ -26,13 +26,14 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_HAS_BEACON_CHAIN=false
# api config # api config
NEXT_PUBLIC_API_HOST=eth-goerli.blockscout.com NEXT_PUBLIC_API_HOST=eth-goerli.blockscout.com
......
...@@ -26,13 +26,14 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH ...@@ -26,13 +26,14 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_HAS_BEACON_CHAIN=false
# api config # api config
NEXT_PUBLIC_API_HOST=blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_API_HOST=blockscout-main.k8s-dev.blockscout.com
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Check run-time ENVs values integrity # Check run-time ENVs values integrity
node "$(dirname "$0")/envs-validator.js" "$input" node "$(dirname "$0")/envs-validator.js" "$input"
if [ $? != 0 ]; then if [ $? != 0 ]; then
echo ENV integrity check failed. 1>&2 && exit 1 echo 🛑 ENV integrity check failed. 1>&2 && exit 1
fi fi
# Execute script for replace build-time ENVs placeholders with their values at runtime # Execute script for replace build-time ENVs placeholders with their values at runtime
......
#!/bin/bash
# Check if the number of arguments provided is correct
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <input_file>"
exit 1
fi
input_file="$1"
prefix="NEXT_PUBLIC_"
# Function to make the environment variables template file
# It will read the input file, extract all prefixed string and use them as variables names
# This variables will have placeholders for their values at buildtime which will be replaced with actual values at runtime
make_envs_template_file() {
output_file=".env.production"
# Check if file already exists and empty its content if it does
if [ -f "$output_file" ]; then
> "$output_file"
fi
grep -oE "${prefix}[[:alnum:]_]+" "$input_file" | sort -u | while IFS= read -r var_name; do
echo "$var_name=__PLACEHOLDER_FOR_${var_name}__" >> "$output_file"
done
}
# Function to save build-time environment variables to .env file
save_build-time_envs() {
output_file=".env"
# Check if file already exists and empty its content if it does or create a new one
if [ -f "$output_file" ]; then
> "$output_file"
else
touch "$output_file"
fi
env | grep "^${prefix}" | while IFS= read -r line; do
echo "$line" >> "$output_file"
done
}
make_envs_template_file
save_build-time_envs
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
set +x set +x
# config # config
envFilename='.env.template' envFilename='.env.production'
nextFolder='./.next/' nextFolder='./.next/'
# replacing build-stage ENVs with run-stage ENVs # replacing build-stage ENVs with run-stage ENVs
......
/node_modules
.env
.env.production
index.js
envs.ts
schema.ts
\ No newline at end of file
#!/bin/bash
cp ../../../types/envs.ts .
export NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD)
export NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0)
../../scripts/make_envs_template.sh ../../../docs/ENVS.md
yarn build
dotenv -e ../../../configs/envs/.env.main -e ../../../configs/envs/.env.secrets yarn validate
\ No newline at end of file
/* eslint-disable no-console */ /* eslint-disable no-console */
import fs from 'fs';
import path from 'path';
import type { ZodError } from 'zod-validation-error'; import type { ZodError } from 'zod-validation-error';
import { fromZodError } from 'zod-validation-error'; import { fromZodError } from 'zod-validation-error';
import { nextPublicEnvsSchema } from './schema'; import { nextPublicEnvsSchema } from './schema';
try { run();
const appEnvs = Object.entries(process.env)
.filter(([ key ]) => key.startsWith('NEXT_PUBLIC_')) async function run() {
.reduce((result, [ key, value ]) => { console.log();
result[key] = value || ''; try {
return result; const appEnvs = Object.entries(process.env)
}, {} as Record<string, string>); .filter(([ key ]) => key.startsWith('NEXT_PUBLIC_'))
.reduce((result, [ key, value ]) => {
console.log(`⏳ Validating environment variables schema...`); result[key] = value || '';
nextPublicEnvsSchema.parse(appEnvs); return result;
console.log('👍 All good!\n'); }, {} as Record<string, string>);
} catch (error) {
const validationError = fromZodError( await validateEnvsSchema(appEnvs);
error as ZodError, await checkPlaceholdersCongruity(appEnvs);
{
prefix: '', } catch (error) {
prefixSeparator: '\n ', process.exit(1);
issueSeparator: ';\n ', }
}, }
);
console.log(validationError); async function validateEnvsSchema(appEnvs: Record<string, string>) {
console.log('🚨 ENV set is invalid\n'); try {
process.exit(1); console.log(`⏳ Validating environment variables schema...`);
nextPublicEnvsSchema.parse(appEnvs);
console.log('👍 All good!\n');
} catch (error) {
const validationError = fromZodError(
error as ZodError,
{
prefix: '',
prefixSeparator: '\n ',
issueSeparator: ';\n ',
},
);
console.log(validationError);
console.log('🚨 Environment variables set is invalid.\n');
throw error;
}
}
async function checkPlaceholdersCongruity(runTimeEnvs: Record<string, string>) {
try {
console.log(`⏳ Checking environment variables and their placeholders congruity...`);
const placeholders = await getEnvsPlaceholders(path.resolve(__dirname, '.env.production'));
const buildTimeEnvs = await getEnvsPlaceholders(path.resolve(__dirname, '.env'));
const envs = Object.keys(runTimeEnvs).filter((env) => !buildTimeEnvs.includes(env));
const inconsistencies: Array<string> = [];
for (const env of envs) {
const hasPlaceholder = placeholders.includes(env);
if (!hasPlaceholder) {
inconsistencies.push(env);
}
}
if (inconsistencies.length > 0) {
console.log(`🚸 For the following environment variables placeholders were not generated at build-time:`);
inconsistencies.forEach((env) => {
console.log(` ${ env }`);
});
console.log('They are either deprecated or running the app with them may lead to unexpected behavior. Please check the documentation for more details.');
throw new Error();
}
console.log('👍 All good!\n');
} catch (error) {
console.log('🚨 Congruity check failed.\n');
throw error;
}
}
function getEnvsPlaceholders(filePath: string): Promise<Array<string>> {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.log(`⛔ Unable to read placeholders file.`);
reject(err);
return;
}
const lines = data.split('\n');
const variables = lines.map(line => {
const variable = line.split('=')[0];
return variable.trim();
});
resolve(variables.filter(Boolean));
});
});
} }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"build": "yarn ts-to-zod ./envs.ts ./schema.ts && yarn webpack-cli -c ./webpack.config.js", "build": "yarn ts-to-zod ./envs.ts ./schema.ts && yarn webpack-cli -c ./webpack.config.js",
"validate": "node ./index.js", "validate": "node ./index.js",
"dev": "cp ../../../types/envs.ts ./ && yarn build && yarn dotenv -e ../../../configs/envs/.env.poa_core yarn validate" "dev": "./dev.sh"
}, },
"dependencies": { "dependencies": {
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
......
{ {
"compilerOptions": { "compilerOptions": {
"target": "es6", "target": "es2016",
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
......
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
mode: 'production', mode: 'production',
target: 'node',
entry: path.resolve(__dirname) + '/index.ts', entry: path.resolve(__dirname) + '/index.ts',
module: { module: {
rules: [ rules: [
......
# Build-time environment variables
These variables are passed to the app during the image build process. They cannot be re-defined during the run-time.
| Variable | Type | Description | Optional | Example value |
| --- | --- | --- | --- | --- |
| NEXT_PUBLIC_GIT_COMMIT_SHA | `string` | SHA of the latest commit in the branch from which image is build | false | `29d0613e` |
| NEXT_PUBLIC_GIT_TAG | `string` | Git tag on the latest commit in the branch from which image is build | true | `v1.0.0` |
\ No newline at end of file
...@@ -58,16 +58,15 @@ For all types of dependencies: ...@@ -58,16 +58,15 @@ For all types of dependencies:
*Note*, if the variable should be exposed to the browser don't forget to add prefix `NEXT_PUBLIC_` to its name. *Note*, if the variable should be exposed to the browser don't forget to add prefix `NEXT_PUBLIC_` to its name.
These are the steps that you have to follow to make everything work: These are the steps that you have to follow to make everything work:
1. Register variable placeholder in file `.env.template`; this is the most important step, without this the app will not receive variable value at run-time 1. First and foremost, document variable in the [/docs/ENVS.md](./ENVS.md) file; provide short description, its expected type, requirement flag, default and example value; **do not skip this step** otherwise the app will not receive variable value at run-time
2. Document variable in the [/docs/ENVS.md](./ENVS.md) file; provide short description, its expected type, requirement flag, default and example value 2. Make sure that you have added a property to React app config (`/configs/app/config.ts`) that is associated with this variable; do not use ENV variable values directly in the application code
3. Make sure that you have added a property to React app config (`/configs/app/config.ts`) that is associated with this variable; do not use ENV variable values directly in the application code 3. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `/configs/envs` where it is needed
4. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `/configs/envs` where it is needed 4. Add the variable to CI configs where it is needed
5. Add the variable to CI configs where it is needed
- `deploy/values/review/values.yaml.gotmpl` - review development environment - `deploy/values/review/values.yaml.gotmpl` - review development environment
- `deploy/values/main/values.yaml` - main development environment - `deploy/values/main/values.yaml` - main development environment
- `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks - `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks
- `deploy/values/l2-optimism-goerli/values.yaml` - main development environment - `deploy/values/l2-optimism-goerli/values.yaml` - main development environment
6. Don't forget to mention in the PR notes that new ENV variable were added 5. Don't forget to mention in the PR notes that new ENV variable were added
## Writing & Running Tests ## Writing & Running Tests
...@@ -137,10 +136,10 @@ We have 3 pre-configured projects. You can run your test with the desired projec ...@@ -137,10 +136,10 @@ We have 3 pre-configured projects. You can run your test with the desired projec
| --- | --- | | --- | --- |
| **Running and building** | | **Running and building** |
| `yarn dev` | run local dev server with user's configuration | | `yarn dev` | run local dev server with user's configuration |
| `yarn dev:<config_name>` | run local dev server with predefined configuration | | `yarn dev:preset <config_preset_name>` | run local dev server with predefined configuration |
| `yarn build:docker` | build a docker image locally | | `yarn build:docker` | build a docker image locally |
| `yarn start:docker:local` | start an application from previously built local docker image with user's configuration | | `yarn start:docker:local` | start an application from previously built local docker image with user's configuration |
| `yarn start:docker:poa_core` | start an application from previously built local docker image with predefined configuration for POA network explorer | | `yarn start:docker:preset <config_preset_name>` | start an application from previously built local docker image with predefined configuration |
| **Linting and formatting** | | **Linting and formatting** |
| `yarn lint:eslint` | lint project files with ESLint | | `yarn lint:eslint` | lint project files with ESLint |
| `yarn lint:eslint:fix` | lint project files with ESLint and automatically fix problems | | `yarn lint:eslint:fix` | lint project files with ESLint and automatically fix problems |
......
# Environment variables # Run-time environment variables
The app instance could be customized by passing following variables to NodeJS environment at runtime. See their list below. The app instance could be customized by passing following variables to NodeJS environment at runtime. See their list below.
......
...@@ -9,22 +9,17 @@ ...@@ -9,22 +9,17 @@
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"dev:localhost": "./node_modules/.bin/dotenv -e ./configs/envs/.env.localhost -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty", "dev:preset": "./tools/scripts/dev.preset.sh",
"dev:main": "./node_modules/.bin/dotenv -e ./configs/envs/.env.main -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty",
"dev:main:L2": "./node_modules/.bin/dotenv -e ./configs/envs/.env.main.L2 -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty",
"dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty",
"dev:eth_goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.eth_goerli -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty",
"dev:eth": "./node_modules/.bin/dotenv -e ./configs/envs/.env.eth -e ./configs/envs/.env.secrets -- bash -c 'next dev -- -p \"$NEXT_PUBLIC_APP_PORT\"' | ./node_modules/.bin/pino-pretty",
"build": "next build", "build": "next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) -t blockscout-frontend ./", "build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend ./",
"start": "next start", "start": "next start",
"start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend", "start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend",
"start:docker:poa_core": "docker run -p 3000:3000 --env-file ./configs/envs/.env.poa_core --env-file ./configs/envs/.env.secrets blockscout-frontend", "start:docker:preset": "./tools/scripts/docker.preset.sh",
"lint:eslint": "./node_modules/.bin/eslint . --ext .js,.jsx,.ts,.tsx", "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:eslint:fix": "./node_modules/.bin/eslint . --ext .js,.jsx,.ts,.tsx --fix", "lint:eslint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:tsc": "./node_modules/.bin/tsc -p ./tsconfig.json", "lint:tsc": "tsc -p ./tsconfig.json",
"prepare": "husky install", "prepare": "husky install",
"format-svg": "./node_modules/.bin/svgo -r ./icons", "format-svg": "svgo -r ./icons",
"test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" ./node_modules/.bin/dotenv -e ./configs/envs/.env.pw -- playwright test -c playwright-ct.config.ts", "test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" ./node_modules/.bin/dotenv -e ./configs/envs/.env.pw -- playwright test -c playwright-ct.config.ts",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && rm -rf ./playwright/.cache && yarn test:pw", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && rm -rf ./playwright/.cache && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.35.1-focal ./playwright/run-tests.sh", "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.35.1-focal ./playwright/run-tests.sh",
......
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: yarn dev:preset <preset_name>"
exit 1
fi
preset_name="$1"
config_file="./configs/envs/.env.${preset_name}"
secrets_file="./configs/envs/.env.secrets"
if [ ! -f "$config_file" ]; then
echo "Error: File '$config_file' not found."
exit 1
fi
if [ ! -f "$secrets_file" ]; then
echo "Error: File '$secrets_file' not found."
exit 1
fi
dotenv \
-v NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD) \
-v NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0) \
-e $config_file \
-e $secrets_file \
-- bash -c 'next dev -- -p $NEXT_PUBLIC_APP_PORT' |
pino-pretty
\ No newline at end of file
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: yarn start:docker:preset <preset_name>"
exit 1
fi
preset_name="$1"
config_file="./configs/envs/.env.${preset_name}"
secrets_file="./configs/envs/.env.secrets"
if [ ! -f "$config_file" ]; then
echo "Error: File '$config_file' not found."
exit 1
fi
if [ ! -f "$secrets_file" ]; then
echo "Error: File '$secrets_file' not found."
exit 1
fi
docker run -p 3000:3000 --env-file $config_file --env-file $secrets_file blockscout-frontend | pino-pretty
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment