1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Add validation to production build script (#15468)

Validation has been added to the build script when the "prod" target is
selected. We now ensure that all expected environment variables are
set, and that no extra environment variables are present (which might
indicate that the wrong configuration file is being used).

The `prod` target uses a new `.metamaskprodrc` configuration file. Each
required variable can be specified either via environment variable or
via this config file. CI will continue set these via environment
variable, but for local manual builds we can use the config file to
simplify the build process and ensure consistency.

A new "dist" target has been added to preserve the ability to build a
"production-like" build without this validation.

The config validation is invoked early in the script, in the CLI
argument parsing step, so that it would fail more quickly. Otherwise
we'd have to wait a few minutes longer for the validation to run.
This required some refactoring, moving functions to the utility module
and moving the config to a dedicated module.

Additionally, support has been added for all environment variables to
be set via the config file. Previously the values `PUBNUB_PUB_KEY`,
`PUBNUB_SUB_KEY`, `SENTRY_DSN`, and `SWAPS_USE_DEV_APIS` could only be
set via environment variable. Now, all of these variables can be set
either way.

Closes #15003
This commit is contained in:
Mark Stacey 2022-08-19 14:16:18 -04:00 committed by GitHub
parent f26d2db338
commit 48c02ef641
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 311 additions and 177 deletions

View File

@ -246,9 +246,25 @@ jobs:
- checkout - checkout
- attach_workspace: - attach_workspace:
at: . at: .
- run: - when:
name: build:dist condition:
command: yarn dist not:
matches:
pattern: /^master$/
value: << pipeline.git.branch >>
steps:
- run:
name: build:dist
command: yarn build dist
- when:
condition:
matches:
pattern: /^master$/
value: << pipeline.git.branch >>
steps:
- run:
name: build:prod
command: yarn build prod
- run: - run:
name: build:debug name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2 command: find dist/ -type f -exec md5sum {} \; | sort -k 2
@ -266,7 +282,7 @@ jobs:
at: . at: .
- run: - run:
name: build:dist name: build:dist
command: yarn build --build-type beta prod command: yarn build --build-type beta dist
- run: - run:
name: build:debug name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2 command: find dist/ -type f -exec md5sum {} \; | sort -k 2
@ -290,7 +306,7 @@ jobs:
at: . at: .
- run: - run:
name: build:dist name: build:dist
command: yarn build --build-type flask prod command: yarn build --build-type flask dist
- run: - run:
name: build:debug name: build:debug
command: find dist/ -type f -exec md5sum {} \; | sort -k 2 command: find dist/ -type f -exec md5sum {} \; | sort -k 2

2
.gitignore vendored
View File

@ -47,7 +47,9 @@ notes.txt
.nyc_output .nyc_output
# MetaMask configuration
.metamaskrc .metamaskrc
.metamaskprodrc
# TypeScript # TypeScript
tsout/ tsout/

View File

@ -5,6 +5,8 @@ SEGMENT_WRITE_KEY=
ONBOARDING_V2= ONBOARDING_V2=
SWAPS_USE_DEV_APIS= SWAPS_USE_DEV_APIS=
COLLECTIBLES_V1= COLLECTIBLES_V1=
PUBNUB_PUB_KEY=
PUBNUB_SUB_KEY=
; Set this to '1' to enable support for Sign-In with Ethereum [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) ; Set this to '1' to enable support for Sign-In with Ethereum [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361)
SIWE_V1= SIWE_V1=

101
development/build/config.js Normal file
View File

@ -0,0 +1,101 @@
const path = require('path');
const { readFile } = require('fs/promises');
const ini = require('ini');
const { BuildType } = require('../lib/build-type');
/**
* Get configuration for non-production builds.
*
* @returns {object} The production configuration.
*/
async function getConfig() {
const configPath = path.resolve(__dirname, '..', '..', '.metamaskrc');
let configContents = '';
try {
configContents = await readFile(configPath, {
encoding: 'utf8',
});
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
return {
COLLECTIBLES_V1: process.env.COLLECTIBLES_V1,
INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
ONBOARDING_V2: process.env.ONBOARDING_V2,
PHISHING_WARNING_PAGE_URL: process.env.PHISHING_WARNING_PAGE_URL,
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY,
SEGMENT_HOST: process.env.SEGMENT_HOST,
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
SENTRY_DSN_DEV:
process.env.SENTRY_DSN_DEV ??
'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496',
SIWE_V1: process.env.SIWE_V1,
SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS,
...ini.parse(configContents),
};
}
/**
* Get configuration for production builds and perform validation.
*
* This function validates that all required variables are present, and that
* the production configuration file doesn't include any extraneous entries.
*
* @param {BuildType} buildType - The current build type (e.g. "main", "flask",
* etc.).
* @returns {object} The production configuration.
*/
async function getProductionConfig(buildType) {
const prodConfigPath = path.resolve(__dirname, '..', '..', '.metamaskprodrc');
let prodConfigContents = '';
try {
prodConfigContents = await readFile(prodConfigPath, {
encoding: 'utf8',
});
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
const prodConfig = {
INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID,
INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID,
INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID,
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY,
SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY,
SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY,
SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY,
SENTRY_DSN: process.env.SENTRY_DSN,
...ini.parse(prodConfigContents),
};
const requiredEnvironmentVariables = {
all: ['PUBNUB_PUB_KEY', 'PUBNUB_SUB_KEY', 'SENTRY_DSN'],
[BuildType.beta]: ['INFURA_BETA_PROJECT_ID', 'SEGMENT_BETA_WRITE_KEY'],
[BuildType.flask]: ['INFURA_FLASK_PROJECT_ID', 'SEGMENT_FLASK_WRITE_KEY'],
[BuildType.main]: ['INFURA_PROD_PROJECT_ID', 'SEGMENT_PROD_WRITE_KEY'],
};
for (const required of [
...requiredEnvironmentVariables.all,
...requiredEnvironmentVariables[buildType],
]) {
if (!prodConfig[required]) {
throw new Error(`Missing '${required}' environment variable`);
}
}
const allValid = Object.values(requiredEnvironmentVariables).flat();
for (const environmentVariable of Object.keys(prodConfig)) {
if (!allValid.includes(environmentVariable)) {
throw new Error(`Invalid environment variable: '${environmentVariable}'`);
}
}
return prodConfig;
}
module.exports = { getConfig, getProductionConfig };

View File

@ -1,27 +1,51 @@
/**
* The build target. This descrbes the overall purpose of the build.
*
* These constants also act as the high-level tasks for the build system (i.e.
* the usual tasks invoked directly via the CLI rather than internally).
*/
const BUILD_TARGETS = { const BUILD_TARGETS = {
DEVELOPMENT: 'dev', DEV: 'dev',
PRODUCTION: 'prod', DIST: 'dist',
E2E_TEST: 'test', PROD: 'prod',
E2E_TEST_DEV: 'testDev', TEST: 'test',
TEST_DEV: 'testDev',
};
/**
* The build environment. This describes the environment this build was produced in.
*/
const ENVIRONMENT = {
DEVELOPMENT: 'development',
PRODUCTION: 'production',
OTHER: 'other',
PULL_REQUEST: 'pull-request',
RELEASE_CANDIDATE: 'release-candidate',
STAGING: 'staging',
TESTING: 'testing',
}; };
const TASKS = { const TASKS = {
...BUILD_TARGETS,
CLEAN: 'clean', CLEAN: 'clean',
DEV: 'dev',
LINT_SCSS: 'lint-scss', LINT_SCSS: 'lint-scss',
MANIFEST_DEV: 'manifest:dev', MANIFEST_DEV: 'manifest:dev',
MANIFEST_PROD: 'manifest:prod', MANIFEST_PROD: 'manifest:prod',
MANIFEST_TEST: 'manifest:test', MANIFEST_TEST: 'manifest:test',
MANIFEST_TEST_DEV: 'manifest:testDev', MANIFEST_TEST_DEV: 'manifest:testDev',
PROD: 'prod',
RELOAD: 'reload', RELOAD: 'reload',
SCRIPTS_PROD: 'scripts:prod',
SCRIPTS_CORE_DEV_STANDARD_ENTRY_POINTS: SCRIPTS_CORE_DEV_STANDARD_ENTRY_POINTS:
'scripts:core:dev:standardEntryPoints', 'scripts:core:dev:standardEntryPoints',
SCRIPTS_CORE_DEV_CONTENTSCRIPT: 'scripts:core:dev:contentscript', SCRIPTS_CORE_DEV_CONTENTSCRIPT: 'scripts:core:dev:contentscript',
SCRIPTS_CORE_DEV_DISABLE_CONSOLE: 'scripts:core:dev:disable-console', SCRIPTS_CORE_DEV_DISABLE_CONSOLE: 'scripts:core:dev:disable-console',
SCRIPTS_CORE_DEV_SENTRY: 'scripts:core:dev:sentry', SCRIPTS_CORE_DEV_SENTRY: 'scripts:core:dev:sentry',
SCRIPTS_CORE_DEV_PHISHING_DETECT: 'scripts:core:dev:phishing-detect', SCRIPTS_CORE_DEV_PHISHING_DETECT: 'scripts:core:dev:phishing-detect',
SCRIPTS_CORE_DIST_STANDARD_ENTRY_POINTS:
'scripts:core:dist:standardEntryPoints',
SCRIPTS_CORE_DIST_CONTENTSCRIPT: 'scripts:core:dist:contentscript',
SCRIPTS_CORE_DIST_DISABLE_CONSOLE: 'scripts:core:dist:disable-console',
SCRIPTS_CORE_DIST_SENTRY: 'scripts:core:dist:sentry',
SCRIPTS_CORE_DIST_PHISHING_DETECT: 'scripts:core:dist:phishing-detect',
SCRIPTS_CORE_PROD_STANDARD_ENTRY_POINTS: SCRIPTS_CORE_PROD_STANDARD_ENTRY_POINTS:
'scripts:core:prod:standardEntryPoints', 'scripts:core:prod:standardEntryPoints',
SCRIPTS_CORE_PROD_CONTENTSCRIPT: 'scripts:core:prod:contentscript', SCRIPTS_CORE_PROD_CONTENTSCRIPT: 'scripts:core:prod:contentscript',
@ -42,14 +66,13 @@ const TASKS = {
SCRIPTS_CORE_TEST_DISABLE_CONSOLE: 'scripts:core:test:disable-console', SCRIPTS_CORE_TEST_DISABLE_CONSOLE: 'scripts:core:test:disable-console',
SCRIPTS_CORE_TEST_SENTRY: 'scripts:core:test:sentry', SCRIPTS_CORE_TEST_SENTRY: 'scripts:core:test:sentry',
SCRIPTS_CORE_TEST_PHISHING_DETECT: 'scripts:core:test:phishing-detect', SCRIPTS_CORE_TEST_PHISHING_DETECT: 'scripts:core:test:phishing-detect',
SCRIPTS_DIST: 'scripts:dist',
STATIC_DEV: 'static:dev', STATIC_DEV: 'static:dev',
STATIC_PROD: 'static:prod', STATIC_PROD: 'static:prod',
STYLES: 'styles', STYLES: 'styles',
STYLES_DEV: 'styles:dev', STYLES_DEV: 'styles:dev',
STYLES_PROD: 'styles:prod', STYLES_PROD: 'styles:prod',
TEST: 'test',
TEST_DEV: 'testDev',
ZIP: 'zip', ZIP: 'zip',
}; };
module.exports = { BUILD_TARGETS, TASKS }; module.exports = { BUILD_TARGETS, ENVIRONMENT, TASKS };

View File

@ -10,7 +10,7 @@ const { hideBin } = require('yargs/helpers');
const { sync: globby } = require('globby'); const { sync: globby } = require('globby');
const { getVersion } = require('../lib/get-version'); const { getVersion } = require('../lib/get-version');
const { BuildType } = require('../lib/build-type'); const { BuildType } = require('../lib/build-type');
const { TASKS } = require('./constants'); const { TASKS, ENVIRONMENT } = require('./constants');
const { const {
createTask, createTask,
composeSeries, composeSeries,
@ -22,7 +22,9 @@ const createScriptTasks = require('./scripts');
const createStyleTasks = require('./styles'); const createStyleTasks = require('./styles');
const createStaticAssetTasks = require('./static'); const createStaticAssetTasks = require('./static');
const createEtcTasks = require('./etc'); const createEtcTasks = require('./etc');
const { getBrowserVersionMap } = require('./utils'); const { getBrowserVersionMap, getEnvironment } = require('./utils');
const { getConfig, getProductionConfig } = require('./config');
const { BUILD_TARGETS } = require('./constants');
// Packages required dynamically via browserify configuration in dependencies // Packages required dynamically via browserify configuration in dependencies
// Required for LavaMoat policy generation // Required for LavaMoat policy generation
@ -50,9 +52,12 @@ require('eslint-plugin-react');
require('eslint-plugin-react-hooks'); require('eslint-plugin-react-hooks');
require('eslint-plugin-jest'); require('eslint-plugin-jest');
defineAndRunBuildTasks(); defineAndRunBuildTasks().catch((error) => {
console.error(error.stack || error);
process.exitCode = 1;
});
function defineAndRunBuildTasks() { async function defineAndRunBuildTasks() {
const { const {
applyLavaMoat, applyLavaMoat,
buildType, buildType,
@ -63,7 +68,7 @@ function defineAndRunBuildTasks() {
shouldLintFenceFiles, shouldLintFenceFiles,
skipStats, skipStats,
version, version,
} = parseArgv(); } = await parseArgv();
const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera']; const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'];
@ -135,6 +140,17 @@ function defineAndRunBuildTasks() {
), ),
); );
// build production-like distributable build
createTask(
TASKS.DIST,
composeSeries(
clean,
styleTasks.prod,
composeParallel(scriptTasks.dist, staticTasks.prod, manifestTasks.prod),
zip,
),
);
// build for prod release // build for prod release
createTask( createTask(
TASKS.PROD, TASKS.PROD,
@ -147,7 +163,7 @@ function defineAndRunBuildTasks() {
); );
// build just production scripts, for LavaMoat policy generation purposes // build just production scripts, for LavaMoat policy generation purposes
createTask(TASKS.SCRIPTS_PROD, scriptTasks.prod); createTask(TASKS.SCRIPTS_DIST, scriptTasks.dist);
// build for CI testing // build for CI testing
createTask( createTask(
@ -164,20 +180,22 @@ function defineAndRunBuildTasks() {
createTask(TASKS.styles, styleTasks.prod); createTask(TASKS.styles, styleTasks.prod);
// Finally, start the build process by running the entry task. // Finally, start the build process by running the entry task.
runTask(entryTask, { skipStats }); await runTask(entryTask, { skipStats });
} }
function parseArgv() { async function parseArgv() {
const { argv } = yargs(hideBin(process.argv)) const { argv } = yargs(hideBin(process.argv))
.usage('$0 <task> [options]', 'Build the MetaMask extension.', (_yargs) => .usage('$0 <task> [options]', 'Build the MetaMask extension.', (_yargs) =>
_yargs _yargs
.positional('task', { .positional('task', {
description: `The task to run. There are a number of main tasks, each of which calls other tasks internally. The main tasks are: description: `The task to run. There are a number of main tasks, each of which calls other tasks internally. The main tasks are:
prod: Create an optimized build for a production environment.
dev: Create an unoptimized, live-reloading build for local development. dev: Create an unoptimized, live-reloading build for local development.
dist: Create an optimized production-like for a non-production environment.
prod: Create an optimized build for a production environment.
test: Create an optimized build for running e2e tests. test: Create an optimized build for running e2e tests.
testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`, testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
@ -254,6 +272,18 @@ testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
const version = getVersion(buildType, buildVersion); const version = getVersion(buildType, buildVersion);
const highLevelTasks = Object.values(BUILD_TARGETS);
if (highLevelTasks.includes(task)) {
const environment = getEnvironment({ buildTarget: task });
if (environment === ENVIRONMENT.PRODUCTION) {
// Output ignored, this is only called to ensure config is validated
await getProductionConfig(buildType);
} else {
// Output ignored, this is only called to ensure config is validated
await getConfig();
}
}
return { return {
applyLavaMoat, applyLavaMoat,
buildType, buildType,

View File

@ -24,45 +24,19 @@ const Sqrl = require('squirrelly');
const lavapack = require('@lavamoat/lavapack'); const lavapack = require('@lavamoat/lavapack');
const lavamoatBrowserify = require('lavamoat-browserify'); const lavamoatBrowserify = require('lavamoat-browserify');
const terser = require('terser'); const terser = require('terser');
const ini = require('ini');
const bifyModuleGroups = require('bify-module-groups'); const bifyModuleGroups = require('bify-module-groups');
const configPath = path.resolve(__dirname, '..', '..', '.metamaskrc');
let configContents = '';
try {
configContents = readFileSync(configPath, {
encoding: 'utf8',
});
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
const metamaskrc = {
INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
INFURA_BETA_PROJECT_ID: process.env.INFURA_BETA_PROJECT_ID,
INFURA_FLASK_PROJECT_ID: process.env.INFURA_FLASK_PROJECT_ID,
INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID,
ONBOARDING_V2: process.env.ONBOARDING_V2,
COLLECTIBLES_V1: process.env.COLLECTIBLES_V1,
PHISHING_WARNING_PAGE_URL: process.env.PHISHING_WARNING_PAGE_URL,
SEGMENT_HOST: process.env.SEGMENT_HOST,
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
SEGMENT_BETA_WRITE_KEY: process.env.SEGMENT_BETA_WRITE_KEY,
SEGMENT_FLASK_WRITE_KEY: process.env.SEGMENT_FLASK_WRITE_KEY,
SEGMENT_PROD_WRITE_KEY: process.env.SEGMENT_PROD_WRITE_KEY,
SENTRY_DSN_DEV:
process.env.SENTRY_DSN_DEV ||
'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496',
SIWE_V1: process.env.SIWE_V1,
...ini.parse(configContents),
};
const { streamFlatMap } = require('../stream-flat-map'); const { streamFlatMap } = require('../stream-flat-map');
const { BuildType } = require('../lib/build-type'); const { BuildType } = require('../lib/build-type');
const { BUILD_TARGETS } = require('./constants'); const { BUILD_TARGETS, ENVIRONMENT } = require('./constants');
const { logError } = require('./utils'); const { getConfig, getProductionConfig } = require('./config');
const {
isDevBuild,
isTestBuild,
getEnvironment,
logError,
} = require('./utils');
const { const {
createTask, createTask,
@ -74,81 +48,28 @@ const {
createRemoveFencedCodeTransform, createRemoveFencedCodeTransform,
} = require('./transforms/remove-fenced-code'); } = require('./transforms/remove-fenced-code');
/**
* The build environment. This describes the environment this build was produced in.
*/
const ENVIRONMENT = {
DEVELOPMENT: 'development',
PRODUCTION: 'production',
OTHER: 'other',
PULL_REQUEST: 'pull-request',
RELEASE_CANDIDATE: 'release-candidate',
STAGING: 'staging',
TESTING: 'testing',
};
/**
* Returns whether the current build is a development build or not.
*
* @param {BUILD_TARGETS} buildTarget - The current build target.
* @returns Whether the current build is a development build.
*/
function isDevBuild(buildTarget) {
return (
buildTarget === BUILD_TARGETS.DEVELOPMENT ||
buildTarget === BUILD_TARGETS.E2E_TEST_DEV
);
}
/**
* Returns whether the current build is an e2e test build or not.
*
* @param {BUILD_TARGETS} buildTarget - The current build target.
* @returns Whether the current build is an e2e test build.
*/
function isTestBuild(buildTarget) {
return (
buildTarget === BUILD_TARGETS.E2E_TEST ||
buildTarget === BUILD_TARGETS.E2E_TEST_DEV
);
}
/**
* Get a value from the configuration, and confirm that it is set.
*
* @param {string} key - The configuration key to retrieve.
* @returns {string} The config entry requested.
* @throws {Error} Throws if the requested key is missing.
*/
function getConfigValue(key) {
const value = metamaskrc[key];
if (!value) {
throw new Error(`Missing config entry for '${key}'`);
}
return value;
}
/** /**
* Get the appropriate Infura project ID. * Get the appropriate Infura project ID.
* *
* @param {object} options - The Infura project ID options. * @param {object} options - The Infura project ID options.
* @param {BuildType} options.buildType - The current build type. * @param {BuildType} options.buildType - The current build type.
* @param {object} options.config - The environment variable configuration.
* @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment. * @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment.
* @param {boolean} options.testing - Whether this is a test build or not. * @param {boolean} options.testing - Whether this is a test build or not.
* @returns {string} The Infura project ID. * @returns {string} The Infura project ID.
*/ */
function getInfuraProjectId({ buildType, environment, testing }) { function getInfuraProjectId({ buildType, config, environment, testing }) {
if (testing) { if (testing) {
return '00000000000000000000000000000000'; return '00000000000000000000000000000000';
} else if (environment !== ENVIRONMENT.PRODUCTION) { } else if (environment !== ENVIRONMENT.PRODUCTION) {
// Skip validation because this is unset on PRs from forks. // Skip validation because this is unset on PRs from forks.
return metamaskrc.INFURA_PROJECT_ID; return config.INFURA_PROJECT_ID;
} else if (buildType === BuildType.main) { } else if (buildType === BuildType.main) {
return getConfigValue('INFURA_PROD_PROJECT_ID'); return config.INFURA_PROD_PROJECT_ID;
} else if (buildType === BuildType.beta) { } else if (buildType === BuildType.beta) {
return getConfigValue('INFURA_BETA_PROJECT_ID'); return config.INFURA_BETA_PROJECT_ID;
} else if (buildType === BuildType.flask) { } else if (buildType === BuildType.flask) {
return getConfigValue('INFURA_FLASK_PROJECT_ID'); return config.INFURA_FLASK_PROJECT_ID;
} }
throw new Error(`Invalid build type: '${buildType}'`); throw new Error(`Invalid build type: '${buildType}'`);
} }
@ -158,19 +79,20 @@ function getInfuraProjectId({ buildType, environment, testing }) {
* *
* @param {object} options - The Segment write key options. * @param {object} options - The Segment write key options.
* @param {BuildType} options.buildType - The current build type. * @param {BuildType} options.buildType - The current build type.
* @param {object} options.config - The environment variable configuration.
* @param {keyof ENVIRONMENT} options.environment - The current build environment. * @param {keyof ENVIRONMENT} options.environment - The current build environment.
* @returns {string} The Segment write key. * @returns {string} The Segment write key.
*/ */
function getSegmentWriteKey({ buildType, environment }) { function getSegmentWriteKey({ buildType, config, environment }) {
if (environment !== ENVIRONMENT.PRODUCTION) { if (environment !== ENVIRONMENT.PRODUCTION) {
// Skip validation because this is unset on PRs from forks, and isn't necessary for development builds. // Skip validation because this is unset on PRs from forks, and isn't necessary for development builds.
return metamaskrc.SEGMENT_WRITE_KEY; return config.SEGMENT_WRITE_KEY;
} else if (buildType === BuildType.main) { } else if (buildType === BuildType.main) {
return getConfigValue('SEGMENT_PROD_WRITE_KEY'); return config.SEGMENT_PROD_WRITE_KEY;
} else if (buildType === BuildType.beta) { } else if (buildType === BuildType.beta) {
return getConfigValue('SEGMENT_BETA_WRITE_KEY'); return config.SEGMENT_BETA_WRITE_KEY;
} else if (buildType === BuildType.flask) { } else if (buildType === BuildType.flask) {
return getConfigValue('SEGMENT_FLASK_WRITE_KEY'); return config.SEGMENT_FLASK_WRITE_KEY;
} }
throw new Error(`Invalid build type: '${buildType}'`); throw new Error(`Invalid build type: '${buildType}'`);
} }
@ -179,11 +101,12 @@ function getSegmentWriteKey({ buildType, environment }) {
* Get the URL for the phishing warning page, if it has been set. * Get the URL for the phishing warning page, if it has been set.
* *
* @param {object} options - The phishing warning page options. * @param {object} options - The phishing warning page options.
* @param {object} options.config - The environment variable configuration.
* @param {boolean} options.testing - Whether this is a test build or not. * @param {boolean} options.testing - Whether this is a test build or not.
* @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set. * @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set.
*/ */
function getPhishingWarningPageUrl({ testing }) { function getPhishingWarningPageUrl({ config, testing }) {
let phishingWarningPageUrl = metamaskrc.PHISHING_WARNING_PAGE_URL; let phishingWarningPageUrl = config.PHISHING_WARNING_PAGE_URL;
if (!phishingWarningPageUrl) { if (!phishingWarningPageUrl) {
phishingWarningPageUrl = testing phishingWarningPageUrl = testing
@ -259,35 +182,35 @@ function createScriptTasks({
shouldLintFenceFiles, shouldLintFenceFiles,
version, version,
}) { }) {
// internal tasks // high level tasks
const core = { return {
// dev tasks (live reload) // dev tasks (live reload)
dev: createTasksForScriptBundles({ dev: createTasksForScriptBundles({
buildTarget: BUILD_TARGETS.DEVELOPMENT, buildTarget: BUILD_TARGETS.DEV,
taskPrefix: 'scripts:core:dev', taskPrefix: 'scripts:core:dev',
}), }),
// production-like distributable build
dist: createTasksForScriptBundles({
buildTarget: BUILD_TARGETS.DIST,
taskPrefix: 'scripts:core:dist',
}),
// production // production
prod: createTasksForScriptBundles({ prod: createTasksForScriptBundles({
buildTarget: BUILD_TARGETS.PRODUCTION, buildTarget: BUILD_TARGETS.PROD,
taskPrefix: 'scripts:core:prod', taskPrefix: 'scripts:core:prod',
}), }),
// built for CI tests // built for CI tests
test: createTasksForScriptBundles({ test: createTasksForScriptBundles({
buildTarget: BUILD_TARGETS.E2E_TEST, buildTarget: BUILD_TARGETS.TEST,
taskPrefix: 'scripts:core:test', taskPrefix: 'scripts:core:test',
}), }),
// built for CI test debugging // built for CI test debugging
testDev: createTasksForScriptBundles({ testDev: createTasksForScriptBundles({
buildTarget: BUILD_TARGETS.E2E_TEST_DEV, buildTarget: BUILD_TARGETS.TEST_DEV,
taskPrefix: 'scripts:core:test-live', taskPrefix: 'scripts:core:test-live',
}), }),
}; };
// high level tasks
const { dev, test, testDev, prod } = core;
return { dev, test, testDev, prod };
/** /**
* Define tasks for building the JavaScript modules used by the extension. * Define tasks for building the JavaScript modules used by the extension.
* This function returns a single task that builds JavaScript modules in * This function returns a single task that builds JavaScript modules in
@ -595,7 +518,7 @@ function createFactoredBuild({
const reloadOnChange = isDevBuild(buildTarget); const reloadOnChange = isDevBuild(buildTarget);
const minify = !isDevBuild(buildTarget); const minify = !isDevBuild(buildTarget);
const envVars = getEnvironmentVariables({ const envVars = await getEnvironmentVariables({
buildTarget, buildTarget,
buildType, buildType,
version, version,
@ -823,11 +746,11 @@ function createNormalBundle({
const minify = Boolean(devMode) === false; const minify = Boolean(devMode) === false;
const envVars = { const envVars = {
...getEnvironmentVariables({ ...(await getEnvironmentVariables({
buildTarget, buildTarget,
buildType, buildType,
version, version,
}), })),
...extraEnvironmentVariables, ...extraEnvironmentVariables,
}; };
setupBundlerDefaults(buildConfiguration, { setupBundlerDefaults(buildConfiguration, {
@ -915,7 +838,7 @@ function setupBundlerDefaults(
}); });
// Ensure react-devtools is only included in dev builds // Ensure react-devtools is only included in dev builds
if (buildTarget !== BUILD_TARGETS.DEVELOPMENT) { if (buildTarget !== BUILD_TARGETS.DEV) {
bundlerOpts.manualIgnore.push('react-devtools'); bundlerOpts.manualIgnore.push('react-devtools');
bundlerOpts.manualIgnore.push('remote-redux-devtools'); bundlerOpts.manualIgnore.push('remote-redux-devtools');
} }
@ -1081,20 +1004,22 @@ async function createBundle(buildConfiguration, { reloadOnChange }) {
* @param {string} options.version - The current version of the extension. * @param {string} options.version - The current version of the extension.
* @returns {object} A map of environment variables to inject. * @returns {object} A map of environment variables to inject.
*/ */
function getEnvironmentVariables({ buildTarget, buildType, version }) { async function getEnvironmentVariables({ buildTarget, buildType, version }) {
const environment = getEnvironment({ buildTarget }); const environment = getEnvironment({ buildTarget });
if (environment === ENVIRONMENT.PRODUCTION && !process.env.SENTRY_DSN) { const config =
throw new Error('Missing SENTRY_DSN environment variable'); environment === ENVIRONMENT.PRODUCTION
} ? await getProductionConfig(buildType)
: await getConfig();
const devMode = isDevBuild(buildTarget); const devMode = isDevBuild(buildTarget);
const testing = isTestBuild(buildTarget); const testing = isTestBuild(buildTarget);
return { return {
COLLECTIBLES_V1: metamaskrc.COLLECTIBLES_V1 === '1', COLLECTIBLES_V1: config.COLLECTIBLES_V1 === '1',
CONF: devMode ? metamaskrc : {}, CONF: devMode ? config : {},
IN_TEST: testing, IN_TEST: testing,
INFURA_PROJECT_ID: getInfuraProjectId({ INFURA_PROJECT_ID: getInfuraProjectId({
buildType, buildType,
config,
environment, environment,
testing, testing,
}), }),
@ -1103,39 +1028,19 @@ function getEnvironmentVariables({ buildTarget, buildType, version }) {
METAMASK_VERSION: version, METAMASK_VERSION: version,
METAMASK_BUILD_TYPE: buildType, METAMASK_BUILD_TYPE: buildType,
NODE_ENV: devMode ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION, NODE_ENV: devMode ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION,
ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1', ONBOARDING_V2: config.ONBOARDING_V2 === '1',
PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ testing }), PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ config, testing }),
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', PUBNUB_PUB_KEY: config.PUBNUB_PUB_KEY || '',
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', PUBNUB_SUB_KEY: config.PUBNUB_SUB_KEY || '',
SEGMENT_HOST: metamaskrc.SEGMENT_HOST, SEGMENT_HOST: config.SEGMENT_HOST,
SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, environment }), SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, config, environment }),
SENTRY_DSN: process.env.SENTRY_DSN, SENTRY_DSN: config.SENTRY_DSN,
SENTRY_DSN_DEV: metamaskrc.SENTRY_DSN_DEV, SENTRY_DSN_DEV: config.SENTRY_DSN_DEV,
SIWE_V1: metamaskrc.SIWE_V1 === '1', SIWE_V1: config.SIWE_V1 === '1',
SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS === '1', SWAPS_USE_DEV_APIS: config.SWAPS_USE_DEV_APIS === '1',
}; };
} }
function getEnvironment({ buildTarget }) {
// get environment slug
if (isDevBuild(buildTarget)) {
return ENVIRONMENT.DEVELOPMENT;
} else if (isTestBuild(buildTarget)) {
return ENVIRONMENT.TESTING;
} else if (process.env.CIRCLE_BRANCH === 'master') {
return ENVIRONMENT.PRODUCTION;
} else if (
/^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH)
) {
return ENVIRONMENT.RELEASE_CANDIDATE;
} else if (process.env.CIRCLE_BRANCH === 'develop') {
return ENVIRONMENT.STAGING;
} else if (process.env.CIRCLE_PULL_REQUEST) {
return ENVIRONMENT.PULL_REQUEST;
}
return ENVIRONMENT.OTHER;
}
function renderHtmlFile({ function renderHtmlFile({
htmlName, htmlName,
groupSet, groupSet,

View File

@ -1,5 +1,30 @@
const semver = require('semver'); const semver = require('semver');
const { BuildType } = require('../lib/build-type'); const { BuildType } = require('../lib/build-type');
const { BUILD_TARGETS, ENVIRONMENT } = require('./constants');
/**
* Returns whether the current build is a development build or not.
*
* @param {BUILD_TARGETS} buildTarget - The current build target.
* @returns Whether the current build is a development build.
*/
function isDevBuild(buildTarget) {
return (
buildTarget === BUILD_TARGETS.DEV || buildTarget === BUILD_TARGETS.TEST_DEV
);
}
/**
* Returns whether the current build is an e2e test build or not.
*
* @param {BUILD_TARGETS} buildTarget - The current build target.
* @returns Whether the current build is an e2e test build.
*/
function isTestBuild(buildTarget) {
return (
buildTarget === BUILD_TARGETS.TEST || buildTarget === BUILD_TARGETS.TEST_DEV
);
}
/** /**
* Map the current version to a format that is compatible with each browser. * Map the current version to a format that is compatible with each browser.
@ -51,6 +76,33 @@ function getBrowserVersionMap(platforms, version) {
}, {}); }, {});
} }
/**
* Get the environment of the current build.
*
* @param {object} options - Build options.
* @param {BUILD_TARGETS} options.buildTarget - The target of the current build.
* @returns {ENVIRONMENT} The current build environment.
*/
function getEnvironment({ buildTarget }) {
// get environment slug
if (buildTarget === BUILD_TARGETS.PROD) {
return ENVIRONMENT.PRODUCTION;
} else if (isDevBuild(buildTarget)) {
return ENVIRONMENT.DEVELOPMENT;
} else if (isTestBuild(buildTarget)) {
return ENVIRONMENT.TESTING;
} else if (
/^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH)
) {
return ENVIRONMENT.RELEASE_CANDIDATE;
} else if (process.env.CIRCLE_BRANCH === 'develop') {
return ENVIRONMENT.STAGING;
} else if (process.env.CIRCLE_PULL_REQUEST) {
return ENVIRONMENT.PULL_REQUEST;
}
return ENVIRONMENT.OTHER;
}
/** /**
* Log an error to the console. * Log an error to the console.
* *
@ -67,5 +119,8 @@ function logError(error) {
module.exports = { module.exports = {
getBrowserVersionMap, getBrowserVersionMap,
getEnvironment,
isDevBuild,
isTestBuild,
logError, logError,
}; };

View File

@ -12,7 +12,7 @@
"start": "yarn build:dev dev --apply-lavamoat=false", "start": "yarn build:dev dev --apply-lavamoat=false",
"start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true",
"start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false", "start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false",
"dist": "yarn build prod", "dist": "yarn build dist",
"build": "yarn lavamoat:build", "build": "yarn lavamoat:build",
"build:dev": "node development/build/index.js", "build:dev": "node development/build/index.js",
"start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build testDev", "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build testDev",