mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
0b71e1a619
The Sentry DSN is now expected to be provided via environment variable for production builds. The build script will fail if it is missing, and an error will be thrown at runtime if it is missing. The `SENTRY_DSN` environment variable has been set in CI to the old value for `SENTRY_PROD_DSN`. We can migrate to a new DSN at some point in the future.
372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
const fs = require('fs')
|
|
const gulp = require('gulp')
|
|
const watch = require('gulp-watch')
|
|
const source = require('vinyl-source-stream')
|
|
const buffer = require('vinyl-buffer')
|
|
const log = require('fancy-log')
|
|
const { assign } = require('lodash')
|
|
const watchify = require('watchify')
|
|
const browserify = require('browserify')
|
|
const envify = require('envify/custom')
|
|
const sourcemaps = require('gulp-sourcemaps')
|
|
const sesify = require('sesify')
|
|
const terser = require('gulp-terser-js')
|
|
const pify = require('pify')
|
|
const endOfStream = pify(require('end-of-stream'))
|
|
const { makeStringTransform } = require('browserify-transform-tools')
|
|
|
|
const conf = require('rc')('metamask', {})
|
|
|
|
const { createTask, composeParallel, composeSeries, runInChildProcess } = require('./task')
|
|
const packageJSON = require('../../package.json')
|
|
|
|
module.exports = createScriptTasks
|
|
|
|
|
|
const dependencies = Object.keys((packageJSON && packageJSON.dependencies) || {})
|
|
const materialUIDependencies = ['@material-ui/core']
|
|
const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/))
|
|
const d3Dependencies = ['c3', 'd3']
|
|
|
|
const externalDependenciesMap = {
|
|
background: [
|
|
'3box',
|
|
],
|
|
ui: [
|
|
...materialUIDependencies, ...reactDepenendencies, ...d3Dependencies,
|
|
],
|
|
}
|
|
|
|
function createScriptTasks ({ browserPlatforms, livereload }) {
|
|
|
|
// internal tasks
|
|
const core = {
|
|
// dev tasks (live reload)
|
|
dev: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:dev', devMode: true }),
|
|
testDev: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:test-live', devMode: true, testing: true }),
|
|
// built for CI tests
|
|
test: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:test', testing: true }),
|
|
// production
|
|
prod: createTasksForBuildJsExtension({ taskPrefix: 'scripts:core:prod' }),
|
|
}
|
|
const deps = {
|
|
background: createTasksForBuildJsDeps({ filename: 'bg-libs', key: 'background' }),
|
|
ui: createTasksForBuildJsDeps({ filename: 'ui-libs', key: 'ui' }),
|
|
}
|
|
|
|
// high level tasks
|
|
|
|
const prod = composeParallel(
|
|
deps.background,
|
|
deps.ui,
|
|
core.prod,
|
|
)
|
|
|
|
const dev = core.dev
|
|
const testDev = core.testDev
|
|
|
|
const test = composeParallel(
|
|
deps.background,
|
|
deps.ui,
|
|
core.test,
|
|
)
|
|
|
|
return { prod, dev, testDev, test }
|
|
|
|
|
|
function createTasksForBuildJsDeps ({ key, filename }) {
|
|
return createTask(`scripts:deps:${key}`, bundleTask({
|
|
label: filename,
|
|
filename: `${filename}.js`,
|
|
buildLib: true,
|
|
dependenciesToBundle: externalDependenciesMap[key],
|
|
devMode: false,
|
|
}))
|
|
}
|
|
|
|
|
|
function createTasksForBuildJsExtension ({ taskPrefix, devMode, testing }) {
|
|
const standardBundles = [
|
|
'background',
|
|
'ui',
|
|
'phishing-detect',
|
|
]
|
|
|
|
const standardSubtasks = standardBundles.map((filename) => {
|
|
return createTask(`${taskPrefix}:${filename}`,
|
|
createBundleTaskForBuildJsExtensionNormal({ filename, devMode, testing }),
|
|
)
|
|
})
|
|
// inpage must be built before contentscript
|
|
// because inpage bundle result is included inside contentscript
|
|
const contentscriptSubtask = createTask(`${taskPrefix}:contentscript`,
|
|
createTaskForBuildJsExtensionContentscript({ devMode, testing }),
|
|
)
|
|
|
|
// task for initiating livereload
|
|
const initiateLiveReload = async () => {
|
|
if (devMode) {
|
|
// trigger live reload when the bundles are updated
|
|
// this is not ideal, but overcomes the limitations:
|
|
// - run from the main process (not child process tasks)
|
|
// - after the first build has completed (thus the timeout)
|
|
// - build tasks never "complete" when run with livereload + child process
|
|
setTimeout(() => {
|
|
watch('./dist/*/*.js', (event) => {
|
|
livereload.changed(event.path)
|
|
})
|
|
}, 75e3)
|
|
}
|
|
}
|
|
|
|
// make each bundle run in a separate process
|
|
const allSubtasks = [...standardSubtasks, contentscriptSubtask].map((subtask) => runInChildProcess(subtask))
|
|
// const allSubtasks = [...standardSubtasks, contentscriptSubtask].map(subtask => (subtask))
|
|
// make a parent task that runs each task in a child thread
|
|
return composeParallel(initiateLiveReload, ...allSubtasks)
|
|
}
|
|
|
|
function createBundleTaskForBuildJsExtensionNormal ({ filename, devMode, testing }) {
|
|
return bundleTask({
|
|
label: filename,
|
|
filename: `${filename}.js`,
|
|
filepath: `./app/scripts/${filename}.js`,
|
|
externalDependencies: devMode ? undefined : externalDependenciesMap[filename],
|
|
devMode,
|
|
testing,
|
|
})
|
|
}
|
|
|
|
function createTaskForBuildJsExtensionContentscript ({ devMode, testing }) {
|
|
const inpage = 'inpage'
|
|
const contentscript = 'contentscript'
|
|
return composeSeries(
|
|
bundleTask({
|
|
label: inpage,
|
|
filename: `${inpage}.js`,
|
|
filepath: `./app/scripts/${inpage}.js`,
|
|
externalDependencies: devMode ? undefined : externalDependenciesMap[inpage],
|
|
devMode,
|
|
testing,
|
|
}),
|
|
bundleTask({
|
|
label: contentscript,
|
|
filename: `${contentscript}.js`,
|
|
filepath: `./app/scripts/${contentscript}.js`,
|
|
externalDependencies: devMode ? undefined : externalDependenciesMap[contentscript],
|
|
devMode,
|
|
testing,
|
|
}),
|
|
)
|
|
}
|
|
|
|
|
|
function bundleTask (opts) {
|
|
let bundler
|
|
|
|
return performBundle
|
|
|
|
async function performBundle () {
|
|
// initialize bundler if not available yet
|
|
// dont create bundler until task is actually run
|
|
if (!bundler) {
|
|
bundler = generateBundler(opts, performBundle)
|
|
// output build logs to terminal
|
|
bundler.on('log', log)
|
|
}
|
|
|
|
let buildStream = bundler.bundle()
|
|
|
|
// handle errors
|
|
buildStream.on('error', (err) => {
|
|
beep()
|
|
if (opts.devMode) {
|
|
console.warn(err.stack)
|
|
} else {
|
|
throw err
|
|
}
|
|
})
|
|
|
|
// process bundles
|
|
buildStream = buildStream
|
|
// convert bundle stream to gulp vinyl stream
|
|
.pipe(source(opts.filename))
|
|
// buffer file contents (?)
|
|
.pipe(buffer())
|
|
|
|
// Initialize Source Maps
|
|
buildStream = buildStream
|
|
// loads map from browserify file
|
|
.pipe(sourcemaps.init({ loadMaps: true }))
|
|
|
|
// Minification
|
|
if (!opts.devMode) {
|
|
buildStream = buildStream
|
|
.pipe(terser({
|
|
mangle: {
|
|
reserved: [ 'MetamaskInpageProvider' ],
|
|
},
|
|
}))
|
|
}
|
|
|
|
// Finalize Source Maps
|
|
if (opts.devMode) {
|
|
// Use inline source maps for development due to Chrome DevTools bug
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=931675
|
|
buildStream = buildStream
|
|
.pipe(sourcemaps.write())
|
|
} else {
|
|
buildStream = buildStream
|
|
.pipe(sourcemaps.write('../sourcemaps'))
|
|
}
|
|
|
|
// write completed bundles
|
|
browserPlatforms.forEach((platform) => {
|
|
const dest = `./dist/${platform}`
|
|
buildStream = buildStream.pipe(gulp.dest(dest))
|
|
})
|
|
|
|
await endOfStream(buildStream)
|
|
}
|
|
}
|
|
|
|
function configureBundleForSesify ({
|
|
browserifyOpts,
|
|
bundleName,
|
|
}) {
|
|
// add in sesify args for better globalRef usage detection
|
|
Object.assign(browserifyOpts, sesify.args)
|
|
|
|
// ensure browserify uses full paths
|
|
browserifyOpts.fullPaths = true
|
|
|
|
// record dependencies used in bundle
|
|
fs.mkdirSync('./sesify', { recursive: true })
|
|
browserifyOpts.plugin.push(['deps-dump', {
|
|
filename: `./sesify/deps-${bundleName}.json`,
|
|
}])
|
|
|
|
const sesifyConfigPath = `./sesify/${bundleName}.json`
|
|
|
|
// add sesify plugin
|
|
browserifyOpts.plugin.push([sesify, {
|
|
writeAutoConfig: sesifyConfigPath,
|
|
}])
|
|
|
|
// remove html comments that SES is alergic to
|
|
const removeHtmlComment = makeStringTransform('remove-html-comment', { excludeExtension: ['.json'] }, (content, _, cb) => {
|
|
const result = content.split('-->').join('-- >')
|
|
cb(null, result)
|
|
})
|
|
browserifyOpts.transform.push([removeHtmlComment, { global: true }])
|
|
}
|
|
|
|
function generateBundler (opts, performBundle) {
|
|
const browserifyOpts = assign({}, watchify.args, {
|
|
plugin: [],
|
|
transform: [],
|
|
debug: true,
|
|
fullPaths: opts.devMode,
|
|
})
|
|
|
|
const bundleName = opts.filename.split('.')[0]
|
|
|
|
// activate sesify
|
|
const activateAutoConfig = Boolean(process.env.SESIFY_AUTOGEN)
|
|
// const activateSesify = activateAutoConfig
|
|
const activateSesify = activateAutoConfig && ['background'].includes(bundleName)
|
|
if (activateSesify) {
|
|
configureBundleForSesify({ browserifyOpts, bundleName })
|
|
}
|
|
|
|
if (!activateSesify) {
|
|
browserifyOpts.plugin.push('browserify-derequire')
|
|
}
|
|
|
|
if (!opts.buildLib) {
|
|
if (opts.devMode && opts.filename === 'ui.js') {
|
|
browserifyOpts['entries'] = ['./development/require-react-devtools.js', opts.filepath]
|
|
} else {
|
|
browserifyOpts['entries'] = [opts.filepath]
|
|
}
|
|
}
|
|
|
|
let bundler = browserify(browserifyOpts)
|
|
.transform('babelify')
|
|
// Transpile any dependencies using the object spread/rest operator
|
|
// because it is incompatible with `esprima`, which is used by `envify`
|
|
// See https://github.com/jquery/esprima/issues/1927
|
|
.transform('babelify', {
|
|
only: [
|
|
'./**/node_modules/libp2p',
|
|
],
|
|
global: true,
|
|
plugins: ['@babel/plugin-proposal-object-rest-spread'],
|
|
})
|
|
.transform('brfs')
|
|
|
|
if (opts.buildLib) {
|
|
bundler = bundler.require(opts.dependenciesToBundle)
|
|
}
|
|
|
|
if (opts.externalDependencies) {
|
|
bundler = bundler.external(opts.externalDependencies)
|
|
}
|
|
|
|
let environment
|
|
if (opts.devMode) {
|
|
environment = 'development'
|
|
} else if (opts.testing) {
|
|
environment = 'testing'
|
|
} else if (process.env.CIRCLE_BRANCH === 'master') {
|
|
environment = 'production'
|
|
} else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) {
|
|
environment = 'release-candidate'
|
|
} else if (process.env.CIRCLE_BRANCH === 'develop') {
|
|
environment = 'staging'
|
|
} else if (process.env.CIRCLE_PULL_REQUEST) {
|
|
environment = 'pull-request'
|
|
} else {
|
|
environment = 'other'
|
|
}
|
|
|
|
if (environment === 'production' && !process.env.SENTRY_DSN) {
|
|
throw new Error('Missing SENTRY_DSN environment variable')
|
|
}
|
|
|
|
// Inject variables into bundle
|
|
bundler.transform(envify({
|
|
METAMASK_DEBUG: opts.devMode,
|
|
METAMASK_ENVIRONMENT: environment,
|
|
METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID,
|
|
NODE_ENV: opts.devMode ? 'development' : 'production',
|
|
IN_TEST: opts.testing ? 'true' : false,
|
|
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
|
|
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
|
|
ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '',
|
|
CONF: opts.devMode ? conf : ({}),
|
|
SENTRY_DSN: process.env.SENTRY_DSN,
|
|
}), {
|
|
global: true,
|
|
})
|
|
|
|
// Live reload - minimal rebundle on change
|
|
if (opts.devMode) {
|
|
bundler = watchify(bundler)
|
|
// on any file update, re-runs the bundler
|
|
bundler.on('update', () => {
|
|
performBundle()
|
|
})
|
|
}
|
|
|
|
return bundler
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
function beep () {
|
|
process.stdout.write('\x07')
|
|
}
|