1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/development/build/index.js
Mark Stacey 48c02ef641
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
2022-08-19 15:46:18 -02:30

331 lines
9.8 KiB
JavaScript
Executable File

//
// build task definitions
//
// run any task with "yarn build ${taskName}"
//
const path = require('path');
const livereload = require('gulp-livereload');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { sync: globby } = require('globby');
const { getVersion } = require('../lib/get-version');
const { BuildType } = require('../lib/build-type');
const { TASKS, ENVIRONMENT } = require('./constants');
const {
createTask,
composeSeries,
composeParallel,
runTask,
} = require('./task');
const createManifestTasks = require('./manifest');
const createScriptTasks = require('./scripts');
const createStyleTasks = require('./styles');
const createStaticAssetTasks = require('./static');
const createEtcTasks = require('./etc');
const { getBrowserVersionMap, getEnvironment } = require('./utils');
const { getConfig, getProductionConfig } = require('./config');
const { BUILD_TARGETS } = require('./constants');
// Packages required dynamically via browserify configuration in dependencies
// Required for LavaMoat policy generation
require('loose-envify');
require('globalthis');
require('@babel/preset-env');
require('@babel/preset-react');
require('@babel/preset-typescript');
require('@babel/core');
// ESLint-related
require('@babel/eslint-parser');
require('@babel/eslint-plugin');
require('@metamask/eslint-config');
require('@metamask/eslint-config-nodejs');
require('@typescript-eslint/parser');
require('eslint');
require('eslint-config-prettier');
require('eslint-import-resolver-node');
require('eslint-import-resolver-typescript');
require('eslint-plugin-import');
require('eslint-plugin-jsdoc');
require('eslint-plugin-node');
require('eslint-plugin-prettier');
require('eslint-plugin-react');
require('eslint-plugin-react-hooks');
require('eslint-plugin-jest');
defineAndRunBuildTasks().catch((error) => {
console.error(error.stack || error);
process.exitCode = 1;
});
async function defineAndRunBuildTasks() {
const {
applyLavaMoat,
buildType,
entryTask,
isLavaMoat,
policyOnly,
shouldIncludeLockdown,
shouldLintFenceFiles,
skipStats,
version,
} = await parseArgv();
const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'];
const browserVersionMap = getBrowserVersionMap(browserPlatforms, version);
const ignoredFiles = getIgnoredFiles(buildType);
const staticTasks = createStaticAssetTasks({
livereload,
browserPlatforms,
shouldIncludeLockdown,
buildType,
});
const manifestTasks = createManifestTasks({
browserPlatforms,
browserVersionMap,
buildType,
});
const styleTasks = createStyleTasks({ livereload });
const scriptTasks = createScriptTasks({
applyLavaMoat,
browserPlatforms,
buildType,
ignoredFiles,
isLavaMoat,
livereload,
policyOnly,
shouldLintFenceFiles,
version,
});
const { clean, reload, zip } = createEtcTasks({
livereload,
browserPlatforms,
buildType,
version,
});
// build for development (livereload)
createTask(
TASKS.DEV,
composeSeries(
clean,
styleTasks.dev,
composeParallel(
scriptTasks.dev,
staticTasks.dev,
manifestTasks.dev,
reload,
),
),
);
// build for test development (livereload)
createTask(
TASKS.TEST_DEV,
composeSeries(
clean,
styleTasks.dev,
composeParallel(
scriptTasks.testDev,
staticTasks.dev,
manifestTasks.testDev,
reload,
),
),
);
// build production-like distributable build
createTask(
TASKS.DIST,
composeSeries(
clean,
styleTasks.prod,
composeParallel(scriptTasks.dist, staticTasks.prod, manifestTasks.prod),
zip,
),
);
// build for prod release
createTask(
TASKS.PROD,
composeSeries(
clean,
styleTasks.prod,
composeParallel(scriptTasks.prod, staticTasks.prod, manifestTasks.prod),
zip,
),
);
// build just production scripts, for LavaMoat policy generation purposes
createTask(TASKS.SCRIPTS_DIST, scriptTasks.dist);
// build for CI testing
createTask(
TASKS.TEST,
composeSeries(
clean,
styleTasks.prod,
composeParallel(scriptTasks.test, staticTasks.prod, manifestTasks.test),
zip,
),
);
// special build for minimal CI testing
createTask(TASKS.styles, styleTasks.prod);
// Finally, start the build process by running the entry task.
await runTask(entryTask, { skipStats });
}
async function parseArgv() {
const { argv } = yargs(hideBin(process.argv))
.usage('$0 <task> [options]', 'Build the MetaMask extension.', (_yargs) =>
_yargs
.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:
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.
testDev: Create an unoptimized, live-reloading build for debugging e2e tests.`,
type: 'string',
})
.option('apply-lavamoat', {
default: true,
description:
'Whether to use LavaMoat. Setting this to `false` can be useful during development if you want to handle LavaMoat errors later.',
type: 'boolean',
})
.option('build-type', {
default: BuildType.main,
description: 'The type of build to create.',
choices: Object.keys(BuildType),
})
.option('build-version', {
default: 0,
description:
'The build version. This is set only for non-main build types. The build version is used in the "prerelease" segment of the extension version, e.g. `[major].[minor].[patch]-[build-type].[build-version]`',
type: 'number',
})
.option('lint-fence-files', {
description:
'Whether files with code fences should be linted after fences have been removed. The build will fail if linting fails. This defaults to `false` if the entry task is `dev` or `testDev`. Otherwise this defaults to `true`.',
type: 'boolean',
})
.option('lockdown', {
default: true,
description:
'Whether to include SES lockdown files in the extension bundle. Setting this to `false` can be useful during development if you want to handle lockdown errors later.',
type: 'boolean',
})
.option('policy-only', {
default: false,
description:
'Stop the build after generating the LavaMoat policy, skipping any writes to disk other than the LavaMoat policy itself.',
type: 'boolean',
})
.option('skip-stats', {
default: false,
description:
'Whether to skip logging the time to completion for each task to the console. This is meant primarily for internal use, to prevent duplicate logging.',
hidden: true,
type: 'boolean',
})
.check((args) => {
if (!Number.isInteger(args.buildVersion)) {
throw new Error(
`Expected integer for 'build-version', got '${args.buildVersion}'`,
);
} else if (!Object.values(TASKS).includes(args.task)) {
throw new Error(`Invalid task: '${args.task}'`);
}
return true;
}),
)
// TODO: Enable `.strict()` after this issue is resolved: https://github.com/LavaMoat/LavaMoat/issues/344
.help('help');
const {
applyLavamoat: applyLavaMoat,
buildType,
buildVersion,
lintFenceFiles,
lockdown,
policyOnly,
skipStats,
task,
} = argv;
// Manually default this to `false` for dev builds only.
const shouldLintFenceFiles = lintFenceFiles ?? !/dev/iu.test(task);
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 {
applyLavaMoat,
buildType,
entryTask: task,
isLavaMoat: process.argv[0].includes('lavamoat'),
policyOnly,
shouldIncludeLockdown: lockdown,
shouldLintFenceFiles,
skipStats,
version,
};
}
/**
* Gets the files to be ignored by the current build, if any.
*
* @param {string} currentBuildType - The type of the current build.
* @returns {string[] | null} The array of files to be ignored by the current
* build, or `null` if no files are to be ignored.
*/
function getIgnoredFiles(currentBuildType) {
const excludedFiles = Object.values(BuildType)
// This filter removes "main" and the current build type. The files of any
// build types that remain in the array will be excluded. "main" is the
// default build type, and has no files that are excluded from other builds.
.filter(
(buildType) =>
buildType !== BuildType.main && buildType !== currentBuildType,
)
// Compute globs targeting files for exclusion for each excluded build
// type.
.reduce((excludedGlobs, excludedBuildType) => {
return excludedGlobs.concat([
`../../app/**/${excludedBuildType}/**`,
`../../shared/**/${excludedBuildType}/**`,
`../../ui/**/${excludedBuildType}/**`,
]);
}, [])
// This creates absolute paths of the form:
// PATH_TO_REPOSITORY_ROOT/app/**/${excludedBuildType}/**
.map((pathGlob) => path.resolve(__dirname, pathGlob));
return globby(excludedFiles);
}