1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 02:10:12 +01:00
metamask-extension/development/build/index.js
Dan J Miller 660ae4c156
Fix mv3 beta build (#18690)
* Build beta with mv3 enabled

* Ensure firefox manifest is an mv2 version

* Revert "Ensure firefox manifest is an mv2 version"

This reverts commit fed74792b0fec33c3a85f2229eb560559d37afe5.

* Only create beta builds for the chrome platform

* Stop linting firefox for beta
2023-04-26 12:26:33 -02:30

428 lines
12 KiB
JavaScript
Executable File

#!/usr/bin/env node
//
// 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 lavapack = require('@lavamoat/lavapack');
const difference = require('lodash/difference');
const { intersection } = require('lodash');
const { getVersion } = require('../lib/get-version');
const { loadBuildTypesConfig } = 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,
shouldIncludeSnow,
shouldLintFenceFiles,
skipStats,
version,
platform,
} = await parseArgv();
const isRootTask = ['dist', 'prod', 'test', 'dev'].includes(entryTask);
if (isRootTask) {
// scuttle on production/tests environment only
const shouldScuttle = ['dist', 'prod', 'test'].includes(entryTask);
console.log(
`Building lavamoat runtime file`,
`(scuttling is ${shouldScuttle ? 'on' : 'off'})`,
);
// build lavamoat runtime file
await lavapack.buildRuntime({
scuttleGlobalThis: applyLavaMoat && shouldScuttle,
scuttleGlobalThisExceptions: [
// globals used by different mm deps outside of lm compartment
'toString',
'getComputedStyle',
'addEventListener',
'removeEventListener',
'ShadowRoot',
'HTMLElement',
'Element',
'pageXOffset',
'pageYOffset',
'visualViewport',
'Reflect',
'Set',
'Object',
'navigator',
'harden',
'console',
'Image', // Used by browser to generate notifications
// globals chrome driver needs to function (test env)
/cdc_[a-zA-Z0-9]+_[a-zA-Z]+/iu,
'performance',
'parseFloat',
'innerWidth',
'innerHeight',
'Symbol',
'Math',
'DOMRect',
'Number',
'Array',
'crypto',
'Function',
'Uint8Array',
'String',
'Promise',
// globals sentry needs to function
'__SENTRY__',
'appState',
'extra',
'stateHooks',
'sentryHooks',
'sentry',
],
});
}
const browserPlatforms = platform ? [platform] : ['firefox', 'chrome'];
const browserVersionMap = getBrowserVersionMap(browserPlatforms, version);
const ignoredFiles = getIgnoredFiles(buildType);
const staticTasks = createStaticAssetTasks({
livereload,
browserPlatforms,
shouldIncludeLockdown,
shouldIncludeSnow,
buildType,
});
const manifestTasks = createManifestTasks({
browserPlatforms,
browserVersionMap,
buildType,
applyLavaMoat,
shouldIncludeSnow,
entryTask,
});
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: loadBuildTypesConfig().default,
description: 'The type of build to create.',
choices: Object.keys(loadBuildTypesConfig().buildTypes),
})
.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('snow', {
default: true,
description:
'Whether to include Snow files in the extension bundle. Setting this to `false` can be useful during development if you want to handle Snow 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',
})
.option('platform', {
default: '',
description:
'Specify a single browser platform to build for. Either `chrome` or `firefox`',
hidden: true,
type: 'string',
})
.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,
snow,
policyOnly,
skipStats,
task,
platform,
} = 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,
shouldIncludeSnow: snow,
shouldLintFenceFiles,
skipStats,
version,
platform,
};
}
/**
* 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 buildConfig = loadBuildTypesConfig();
const cwd = process.cwd();
const exclusiveAssetsForFeatures = (features) =>
globby(
features
.flatMap(
(feature) =>
buildConfig.features[feature].assets
?.filter((asset) => 'exclusiveInclude' in asset)
.map((asset) => asset.exclusiveInclude) ?? [],
)
.map((pathGlob) => path.resolve(cwd, pathGlob)),
);
const allFeatures = Object.keys(buildConfig.features);
const activeFeatures =
buildConfig.buildTypes[currentBuildType].features ?? [];
const inactiveFeatures = difference(allFeatures, activeFeatures);
const ignoredPaths = exclusiveAssetsForFeatures(inactiveFeatures);
// We do a sanity check to verify that any inactive feature haven't excluded files
// that active features are trying to include
const activePaths = exclusiveAssetsForFeatures(activeFeatures);
const conflicts = intersection(activePaths, ignoredPaths);
if (conflicts.length !== 0) {
throw new Error(`Below paths are required exclusively by both active and inactive features resulting in a conflict:
\t-> ${conflicts.join('\n\t-> ')}
Please fix builds.yml`);
}
return ignoredPaths;
}