mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
* feat: add yaml feature management Add yaml feature file per build type. Also add method to parse yaml and set enabled features env to true. The build process will then replace any process.env[feature] that exists on the config by its value * chore: add example for desktop * Added initial draft of build features * [TMP] Sync between computers * Is able to succesfully build stable extension with snaps feature * Removing var context from builds.yml * Add asssets to builds.yml * Minor bug fixes and removing debug logs * [WIP] Test changes * Removed TODOs * Fix regession bug Also * remove debug logs * merge Variables.set and Variables.setMany with an overload * Fix build, lint and a bunch of issues * Update LavaMoat policies * Re-add desktop build type * Fix some tests * Fix desktop build * Define some env variables used by MV3 * Fix lint * Fix remove-fenced-code tests * Fix README typo * Move new code * Fix missing asset copy * Move Jest env setup * Fix path for test after rebase * Fix code fences * Fix fencing and LavaMoat policies * Fix MMI code-fencing after rebase * Fix MMI code fencing after merge * Fix more MMI code fencing --------- Co-authored-by: cryptotavares <joao.tavares@consensys.net> Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com> Co-authored-by: Brad Decker <bhdecker84@gmail.com>
418 lines
12 KiB
JavaScript
Executable File
418 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,
|
|
} = 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 = ['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',
|
|
})
|
|
.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,
|
|
} = 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,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|