mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-24 19:10:22 +01:00
e1722d773a
* Update sentry/cli to 2.19.4 * Ensure sentry files are loaded and referenced with a valid url * Temp to eliminate errors in sentry (should be split into other PRs)
444 lines
13 KiB
JavaScript
Executable File
444 lines
13 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 { BUILD_TARGETS, TASKS } = 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 } = require('./config');
|
|
|
|
// 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 = Object.values(BUILD_TARGETS).includes(entryTask);
|
|
|
|
if (isRootTask) {
|
|
// scuttle on production/tests environment only
|
|
const shouldScuttle = entryTask !== BUILD_TARGETS.DEV;
|
|
|
|
let 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',
|
|
'WeakSet',
|
|
'Event',
|
|
'Image', // Used by browser to generate notifications
|
|
// globals chromedriver needs to function
|
|
/cdc_[a-zA-Z0-9]+_[a-zA-Z]+/iu,
|
|
'performance',
|
|
'parseFloat',
|
|
'innerWidth',
|
|
'innerHeight',
|
|
'Symbol',
|
|
'Math',
|
|
'DOMRect',
|
|
'Number',
|
|
'Array',
|
|
'crypto',
|
|
'Function',
|
|
'Uint8Array',
|
|
'String',
|
|
'Promise',
|
|
'JSON',
|
|
'Date',
|
|
// globals sentry needs to function
|
|
'__SENTRY__',
|
|
'appState',
|
|
'extra',
|
|
'stateHooks',
|
|
'sentryHooks',
|
|
'sentry',
|
|
];
|
|
|
|
if (
|
|
entryTask === BUILD_TARGETS.TEST ||
|
|
entryTask === BUILD_TARGETS.TEST_DEV
|
|
) {
|
|
scuttleGlobalThisExceptions = [
|
|
...scuttleGlobalThisExceptions,
|
|
// more globals chromedriver needs to function
|
|
// in the future, more of the globals above can be put in this list
|
|
'Proxy',
|
|
'ret_nodes',
|
|
];
|
|
}
|
|
|
|
console.log(
|
|
`Building lavamoat runtime file`,
|
|
`(scuttling is ${shouldScuttle ? 'on' : 'off'})`,
|
|
);
|
|
|
|
// build lavamoat runtime file
|
|
await lavapack.buildRuntime({
|
|
scuttleGlobalThis: {
|
|
enabled: applyLavaMoat && shouldScuttle,
|
|
scuttlerName: 'SCUTTLER',
|
|
exceptions: scuttleGlobalThisExceptions,
|
|
},
|
|
});
|
|
}
|
|
|
|
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 });
|
|
// Output ignored, this is only called to ensure config is validated
|
|
await getConfig(buildType, environment);
|
|
}
|
|
|
|
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;
|
|
}
|