1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

Build system refactor (#8140)

* build - start static asset task cleanup

* build - simplify manifest tasks

* build - refactor + rename some tasks

* build - various cleanups

* manifest - fix ref from controller

* build - drop gulp for simple async tasks

* build - breakout gulpfile into multiple files

* build - rename some tasks

* build - use task fn refs instead of string names

* build - bundle all scripts first, except for contentscript

* build - improve task timeline

* deps - update lock

* build - improve task time printout

* build/scripts - remove intermediate named task

* build - use 'yarn build' for task entry points

* build - properly run tasks via runTask for timeline display

* development/announcer - fix manifest path + clean

* build - lint fix

* build - make all defined tasks possible entry points

* build/task - properly report errors during task

* ci - fix sesify/lavamoat-viz build command

* build/scripts - run each bundle in separate processes

* lint fix

* build - forward childProcess logs to console

* build/task - fix parallel/series stream end event

* build/scripts refactor contentscript+inpage into a single task

* build/static - use the fs for 150x speedup zomg

* lint fix

* build/static - fix css copy

* Update development/build/scripts.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/scripts.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/index.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* deps - remove redundant mkdirp

* deps - remove unused pumpify

* deps - remove redundant merge-deep

* deps - prefer is-stream of isstream

* deps - remove clone for lodash.cloneDeep

* clean - remove commented code

* build/static - use fs.copy + fast-glob instead of linux cp for better platform support

* build/manifest - standardize task naming

* build/display - clean - remove unused code

* bugfix - fix fs.promises import

* build - create "clean" as named task for use as entrypoint

* build/static - fix for copying dirs

* Update development/build/task.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/display.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/display.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/display.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* build - use task refs, tasks only return promises not streams, etc

* lint fi bad merge + lint

* build - one last cleanup + refactor

* build - add comments introducing file

* build/manifest - fix bug + subtasks dont beed to be named

* Update package.json

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* build/task - remove unused fn

* Update package.json

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/styles.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

* Update development/build/styles.js

Co-Authored-By: Mark Stacey <markjstacey@gmail.com>

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
kumavis 2020-03-09 08:55:02 +08:00 committed by GitHub
parent 2df8b85c5f
commit 7686edadb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1110 additions and 807 deletions

View File

@ -9,5 +9,5 @@ set -o pipefail
mkdir -p ./build-artifacts/deps-viz/
# generate viz
SESIFY_AUTOGEN=1 npx gulp build:extension:js:background
SESIFY_AUTOGEN=1 yarn build scripts:core:prod:background
npx sesify-viz --deps sesify/deps-background.json --config sesify/background.json --dest ./build-artifacts/deps-viz/background

View File

@ -24,16 +24,9 @@
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"applications": {
"gecko": {
"id": "webextension@metamask.io",
"strict_min_version": "56.0"
}
},
"default_locale": "en",
"background": {
"scripts": [
"chromereload.js",
"bg-libs.js",
"background.js"
],

1
app/manifest/brave.json Normal file
View File

@ -0,0 +1 @@
{}

3
app/manifest/chrome.json Normal file
View File

@ -0,0 +1,3 @@
{
"minimum_chrome_version": "58"
}

View File

@ -0,0 +1,8 @@
{
"applications": {
"gecko": {
"id": "webextension@metamask.io",
"strict_min_version": "56.0"
}
}
}

9
app/manifest/opera.json Normal file
View File

@ -0,0 +1,9 @@
{
"permissions": [
"storage",
"tabs",
"clipboardWrite",
"clipboardRead",
"http://localhost:8545/"
]
}

View File

@ -50,7 +50,7 @@ import accountImporter from './account-import-strategies'
import getBuyEthUrl from './lib/buy-eth-url'
import selectChainId from './lib/select-chain-id'
import { Mutex } from 'await-semaphore'
import { version } from '../manifest.json'
import { version } from '../manifest/_base.json'
import ethUtil, { BN } from 'ethereumjs-util'
const GWEI_BN = new BN('1000000000')

View File

@ -1,14 +1,10 @@
const manifest = require('../app/manifest.json')
const version = manifest.version
const fs = require('fs')
const path = require('path')
const { version } = require('../app/manifest/_base.json')
const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString()
const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md'), 'utf8')
const log = changelog.split(version)[1].split('##')[0].trim()
const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}`
console.log(msg)

View File

@ -0,0 +1,150 @@
const randomColor = require('randomcolor')
const chalk = require('chalk')
module.exports = { setupTaskDisplay, displayChart }
const SYMBOLS = {
Empty: '',
Space: ' ',
Full: '█',
SevenEighths: '▉',
ThreeQuarters: '▊',
FiveEighths: '▋',
Half: '▌',
ThreeEighths: '▍',
Quarter: '▎',
Eighth: '▏',
RightHalf: '▐',
RightEigth: '▕',
}
function setupTaskDisplay (taskEvents) {
const taskData = []
taskEvents.on('start', ([name]) => {
console.log(`Starting '${name}'...`)
})
taskEvents.on('end', ([name, start, end]) => {
taskData.push([name, start, end])
console.log(`Finished '${name}'`)
})
taskEvents.on('complete', () => {
displayChart(taskData)
})
}
function displayChart (data) {
// sort tasks by start time
data.sort((a, b,) => a[1] - b[1])
// get bounds
const first = Math.min(...data.map((entry) => entry[1]))
const last = Math.max(...data.map((entry) => entry[2]))
// get colors
const colors = randomColor({ count: data.length })
// some heading before the bars
console.log(`\nbuild completed. task timeline:`)
// build bars for bounds
data.map((entry, index) => {
const [label, start, end] = entry
const [start2, end2] = [start, end].map((value) => adjust(value, first, last, 40))
const barString = barBuilder(start2, end2)
const color = colors[index]
const coloredBarString = colorize(color, barString)
const duration = ((end - start) / 1e3).toFixed(1)
console.log(coloredBarString, `${label} ${duration}s`)
})
}
function colorize (color, string) {
const colorizer = (typeof chalk[color] === 'function') ? chalk[color] : chalk.hex(color)
return colorizer(string)
}
// scale number within bounds
function adjust (value, first, last, size) {
const length = last - first
const result = (value - first) / length * size
return result
}
// draw bars
function barBuilder (start, end) {
const [spaceInt, spaceRest] = splitNumber(start)
const barBodyLength = end - spaceInt
let [barInt, barRest] = splitNumber(barBodyLength)
// We are handling zero value as a special case
// to print at least something on the screen
if (barInt === 0 && barRest === 0) {
barInt = 0
barRest = 0.001
}
const spaceFull = SYMBOLS.Space.repeat(spaceInt)
const spacePartial = getSymbolNormalRight(spaceRest)
const barFull = SYMBOLS.Full.repeat(barInt)
const barPartial = getSymbolNormal(barRest)
return `${spaceFull}${spacePartial}${barFull}${barPartial}`
}
// get integer and remainder
function splitNumber (value = 0) {
const [int, rest = '0'] = value.toString().split('.')
const int2 = parseInt(int, 10)
const rest2 = parseInt(rest, 10) / Math.pow(10, rest.length)
return [int2, rest2]
}
// get partial block char for value (left-adjusted)
function getSymbolNormal (value) {
// round to closest supported value
const possibleValues = [0, 1 / 8, 1 / 4, 3 / 8, 1 / 2, 5 / 8, 3 / 4, 7 / 8, 1]
const rounded = possibleValues.reduce((prev, curr) => {
return (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev)
})
if (rounded === 0) {
return SYMBOLS.Empty
} else if (rounded === 1 / 8) {
return SYMBOLS.Eighth
} else if (rounded === 1 / 4) {
return SYMBOLS.Quarter
} else if (rounded === 3 / 8) {
return SYMBOLS.ThreeEighths
} else if (rounded === 1 / 2) {
return SYMBOLS.Half
} else if (rounded === 5 / 8) {
return SYMBOLS.FiveEighths
} else if (rounded === 3 / 4) {
return SYMBOLS.ThreeQuarters
} else if (rounded === 7 / 8) {
return SYMBOLS.SevenEighths
} else {
return SYMBOLS.Full
}
}
// get partial block char for value (right-adjusted)
function getSymbolNormalRight (value) {
// round to closest supported value (not much :/)
const possibleValues = [0, 1 / 2, 7 / 8, 1]
const rounded = possibleValues.reduce((prev, curr) => {
return (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev)
})
if (rounded === 0) {
return SYMBOLS.Full
} else if (rounded === 1 / 2) {
return SYMBOLS.RightHalf
} else if (rounded === 7 / 8) {
return SYMBOLS.RightEigth
} else if (rounded === 1) {
return SYMBOLS.Space
} else {
throw new Error('getSymbolNormalRight got unexpected result')
}
}

42
development/build/etc.js Normal file
View File

@ -0,0 +1,42 @@
const gulp = require('gulp')
const gulpZip = require('gulp-zip')
const del = require('del')
const { promises: fs } = require('fs')
const pify = require('pify')
const pump = pify(require('pump'))
const baseManifest = require('../../app/manifest/_base.json')
const { createTask, composeParallel } = require('./task')
module.exports = createEtcTasks
function createEtcTasks ({ browserPlatforms, livereload }) {
const clean = createTask('clean', async function clean () {
await del(['./dist/*'])
await Promise.all(browserPlatforms.map(async (platform) => {
await fs.mkdir(`./dist/${platform}`, { recursive: true })
}))
})
const reload = createTask('reload', function devReload () {
livereload.listen({ port: 35729 })
})
// zip tasks for distribution
const zip = createTask('zip', composeParallel(
...browserPlatforms.map((platform) => createZipTask(platform))
))
return { clean, reload, zip }
}
function createZipTask (target) {
return async () => {
await pump(
gulp.src(`dist/${target}/**`),
gulpZip(`metamask-${target}-${baseManifest.version}.zip`),
gulp.dest('builds'),
)
}
}

91
development/build/index.js Executable file
View File

@ -0,0 +1,91 @@
//
// build task definitions
//
// run any task with "yarn build ${taskName}"
//
const livereload = require('gulp-livereload')
const { createTask, composeSeries, composeParallel, detectAndRunEntryTask } = require('./task')
const createManifestTasks = require('./manifest')
const createScriptTasks = require('./scripts')
const createStyleTasks = require('./styles')
const createStaticAssetTasks = require('./static')
const createEtcTasks = require('./etc')
const browserPlatforms = [
'firefox',
'chrome',
'brave',
'opera',
]
defineAllTasks()
detectAndRunEntryTask()
function defineAllTasks () {
const staticTasks = createStaticAssetTasks({ livereload, browserPlatforms })
const manifestTasks = createManifestTasks({ browserPlatforms })
const styleTasks = createStyleTasks({ livereload })
const scriptTasks = createScriptTasks({ livereload, browserPlatforms })
const { clean, reload, zip } = createEtcTasks({ livereload, browserPlatforms })
// build for development (livereload)
createTask('dev',
composeSeries(
clean,
styleTasks.dev,
composeParallel(
scriptTasks.dev,
staticTasks.dev,
manifestTasks.dev,
reload
)
)
)
// build for test development (livereload)
createTask('testDev',
composeSeries(
clean,
styleTasks.dev,
composeParallel(
scriptTasks.testDev,
staticTasks.dev,
manifestTasks.testDev,
reload
)
)
)
// build for prod release
createTask('prod',
composeSeries(
clean,
styleTasks.prod,
composeParallel(
scriptTasks.prod,
staticTasks.prod,
manifestTasks.prod,
),
zip,
)
)
// build for CI testing
createTask('test',
composeSeries(
clean,
styleTasks.prod,
composeParallel(
scriptTasks.test,
staticTasks.prod,
manifestTasks.test,
),
)
)
// special build for minimal CI testing
createTask('styles', styleTasks.prod)
}

View File

@ -0,0 +1,97 @@
const { promises: fs } = require('fs')
const { merge, cloneDeep } = require('lodash')
const baseManifest = require('../../app/manifest/_base.json')
const { createTask, composeSeries } = require('./task')
module.exports = createManifestTasks
const scriptsToExcludeFromBackgroundDevBuild = {
'bg-libs.js': true,
}
function createManifestTasks ({ browserPlatforms }) {
// merge base manifest with per-platform manifests
const prepPlatforms = async () => {
return Promise.all(browserPlatforms.map(async (platform) => {
const platformModifications = await readJson(`${__dirname}/../../app/manifest/${platform}.json`)
const result = merge(cloneDeep(baseManifest), platformModifications)
const dir = `./dist/${platform}`
await fs.mkdir(dir, { recursive: true })
await writeJson(result, `${dir}/manifest.json`)
}))
}
// dev: remove bg-libs, add chromereload, add perms
const envDev = createTaskForModifyManifestForEnvironment((manifest) => {
const scripts = manifest.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName])
scripts.push('chromereload.js')
manifest.background = {
...manifest.background,
scripts,
}
manifest.permissions = [...manifest.permissions, 'webRequestBlocking']
})
// testDev: remove bg-libs, add perms
const envTestDev = createTaskForModifyManifestForEnvironment((manifest) => {
const scripts = manifest.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName])
scripts.push('chromereload.js')
manifest.background = {
...manifest.background,
scripts,
}
manifest.permissions = [...manifest.permissions, 'webRequestBlocking', 'http://localhost/*']
})
// test: add permissions
const envTest = createTaskForModifyManifestForEnvironment((manifest) => {
manifest.permissions = [...manifest.permissions, 'webRequestBlocking', 'http://localhost/*']
})
// high level manifest tasks
const dev = createTask('manifest:dev', composeSeries(
prepPlatforms,
envDev,
))
const testDev = createTask('manifest:testDev', composeSeries(
prepPlatforms,
envTestDev,
))
const test = createTask('manifest:test', composeSeries(
prepPlatforms,
envTest,
))
const prod = createTask('manifest:prod', prepPlatforms)
return { prod, dev, testDev, test }
// helper for modifying each platform's manifest.json in place
function createTaskForModifyManifestForEnvironment (transformFn) {
return () => {
return Promise.all(browserPlatforms.map(async (platform) => {
const path = `./dist/${platform}/manifest.json`
const manifest = await readJson(path)
transformFn(manifest)
await writeJson(manifest, path)
}))
}
}
}
// helper for reading and deserializing json from fs
async function readJson (path) {
return JSON.parse(await fs.readFile(path, 'utf8'))
}
// helper for serializing and writing json to fs
async function writeJson (obj, path) {
return fs.writeFile(path, JSON.stringify(obj, null, 2))
}

View File

@ -0,0 +1,362 @@
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 { 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'
}
// Inject variables into bundle
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment,
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 || '',
}), {
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')
}

View File

@ -0,0 +1,95 @@
const fs = require('fs-extra')
const path = require('path')
const watch = require('gulp-watch')
const glob = require('fast-glob')
const { createTask, composeSeries } = require('./task')
module.exports = createStaticAssetTasks
const copyTargets = [
{
src: `./app/_locales/`,
dest: `_locales`,
},
{
src: `./app/images/`,
dest: `images`,
},
{
src: `./node_modules/eth-contract-metadata/images/`,
dest: `images/contract`,
},
{
src: `./app/fonts/`,
dest: `fonts`,
},
{
src: `./app/vendor/`,
dest: `vendor`,
},
{
src: `./ui/app/css/output/`,
pattern: `*.css`,
dest: ``,
},
{
src: `./app/`,
pattern: `*.html`,
dest: ``,
},
]
const copyTargetsDev = [
...copyTargets,
{
src: './app/scripts/',
pattern: '/chromereload.js',
dest: ``,
},
]
function createStaticAssetTasks ({ livereload, browserPlatforms }) {
const prod = createTask('static:prod', composeSeries(...copyTargets.map((target) => {
return async function copyStaticAssets () {
await performCopy(target)
}
})))
const dev = createTask('static:dev', composeSeries(...copyTargetsDev.map((target) => {
return async function copyStaticAssets () {
await setupLiveCopy(target)
}
})))
return { dev, prod }
async function setupLiveCopy (target) {
const pattern = target.pattern || '/**/*'
watch(target.src + pattern, (event) => {
livereload.changed(event.path)
performCopy(target)
})
await performCopy(target)
}
async function performCopy (target) {
await Promise.all(browserPlatforms.map(async (platform) => {
if (target.pattern) {
await copyGlob(target.src, `${target.src}${target.pattern}`, `./dist/${platform}/${target.dest}`)
} else {
await copyGlob(target.src, `${target.src}`, `./dist/${platform}/${target.dest}`)
}
}))
}
async function copyGlob (baseDir, srcGlob, dest) {
const sources = await glob(srcGlob, { onlyFiles: false })
await Promise.all(sources.map(async (src) => {
const relativePath = path.relative(baseDir, src)
await fs.copy(src, `${dest}${relativePath}`)
}))
}
}

View File

@ -0,0 +1,79 @@
const pify = require('pify')
const gulp = require('gulp')
const sass = require('gulp-sass')
sass.compiler = require('node-sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const watch = require('gulp-watch')
const sourcemaps = require('gulp-sourcemaps')
const rtlcss = require('gulp-rtlcss')
const rename = require('gulp-rename')
const pump = pify(require('pump'))
const { createTask } = require('./task')
// scss compilation and autoprefixing tasks
module.exports = createStyleTasks
function createStyleTasks ({ livereload }) {
const prod = createTask('styles:prod', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: false,
}))
const dev = createTask('styles:dev', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: true,
pattern: 'ui/app/**/*.scss',
}))
const lint = createTask('lint-scss', function () {
return gulp
.src('ui/app/css/itcss/**/*.scss')
.pipe(gulpStylelint({
reporters: [
{ formatter: 'string', console: true },
],
fix: true,
}))
})
return { prod, dev, lint }
function createScssBuildTask ({ src, dest, devMode, pattern }) {
return async function () {
if (devMode) {
watch(pattern, async (event) => {
await buildScss(devMode)
livereload.changed(event.path)
})
}
await buildScss(devMode)
}
async function buildScss (devMode) {
await pump(...[
// pre-process
gulp.src(src),
devMode && sourcemaps.init(),
sass().on('error', sass.logError),
devMode && sourcemaps.write(),
autoprefixer(),
// standard
gulp.dest(dest),
// right-to-left
rtlcss(),
rename({ suffix: '-rtl' }),
devMode && sourcemaps.write(),
gulp.dest(dest),
].filter(Boolean))
}
}
}

101
development/build/task.js Normal file
View File

@ -0,0 +1,101 @@
const EventEmitter = require('events')
const { spawn } = require('child_process')
const tasks = {}
const taskEvents = new EventEmitter()
module.exports = { detectAndRunEntryTask, tasks, taskEvents, createTask, runTask, composeSeries, composeParallel, runInChildProcess }
const { setupTaskDisplay } = require('./display')
function detectAndRunEntryTask () {
// get requested task name and execute
const taskName = process.argv[2]
if (!taskName) {
throw new Error(`MetaMask build: No task name specified`)
}
const skipStats = process.argv[3] === '--skip-stats'
runTask(taskName, { skipStats })
}
async function runTask (taskName, { skipStats } = {}) {
if (!(taskName in tasks)) {
throw new Error(`MetaMask build: Unrecognized task name "${taskName}"`)
}
if (!skipStats) {
setupTaskDisplay(taskEvents)
console.log(`running task "${taskName}"...`)
}
try {
await tasks[taskName]()
} catch (err) {
console.error(`MetaMask build: Encountered an error while running task "${taskName}".`)
console.error(err)
process.exit(1)
}
taskEvents.emit('complete')
}
function createTask (taskName, taskFn) {
if (taskName in tasks) {
throw new Error(`MetaMask build: task "${taskName}" already exists. Refusing to redefine`)
}
const task = instrumentForTaskStats(taskName, taskFn)
task.taskName = taskName
tasks[taskName] = task
return task
}
function runInChildProcess (task) {
const taskName = typeof task === 'string' ? task : task.taskName
if (!taskName) {
throw new Error(`MetaMask build: runInChildProcess unable to identify task name`)
}
return instrumentForTaskStats(taskName, async () => {
const childProcess = spawn('yarn', ['build', taskName, '--skip-stats'])
// forward logs to main process
// skip the first stdout event (announcing the process command)
childProcess.stdout.once('data', () => {
childProcess.stdout.on('data', (data) => process.stdout.write(`${taskName}: ${data}`))
})
childProcess.stderr.on('data', (data) => process.stderr.write(`${taskName}: ${data}`))
// await end of process
await new Promise((resolve, reject) => {
childProcess.once('close', (errCode) => {
if (errCode !== 0) {
reject(new Error(`MetaMask build: runInChildProcess for task "${taskName}" encountered an error`))
return
}
resolve()
})
})
})
}
function instrumentForTaskStats (taskName, asyncFn) {
return async () => {
const start = Date.now()
taskEvents.emit('start', [taskName, start])
await asyncFn()
const end = Date.now()
taskEvents.emit('end', [taskName, start, end])
}
}
function composeSeries (...subtasks) {
return async () => {
const realTasks = subtasks
for (const subtask of realTasks) {
await subtask()
}
}
}
function composeParallel (...subtasks) {
return async () => {
const realTasks = subtasks
await Promise.all(realTasks.map((subtask) => subtask()))
}
}

View File

@ -1,724 +0,0 @@
const fs = require('fs')
const watchify = require('watchify')
const browserify = require('browserify')
const envify = require('envify/custom')
const gulp = require('gulp')
const source = require('vinyl-source-stream')
const buffer = require('vinyl-buffer')
const log = require('fancy-log')
const watch = require('gulp-watch')
const sourcemaps = require('gulp-sourcemaps')
const jsoneditor = require('gulp-json-editor')
const zip = require('gulp-zip')
const { assign } = require('lodash')
const livereload = require('gulp-livereload')
const del = require('del')
const manifest = require('./app/manifest.json')
const sass = require('gulp-sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const terser = require('gulp-terser-js')
const pify = require('pify')
const rtlcss = require('gulp-rtlcss')
const rename = require('gulp-rename')
const gulpMultiProcess = require('gulp-multi-process')
const endOfStream = pify(require('end-of-stream'))
const sesify = require('sesify')
const imagemin = require('gulp-imagemin')
const { makeStringTransform } = require('browserify-transform-tools')
const packageJSON = require('./package.json')
sass.compiler = require('node-sass')
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 gulpParallel (...args) {
return function spawnGulpChildProcess (cb) {
return gulpMultiProcess(args, cb, true)
}
}
const browserPlatforms = [
'firefox',
'chrome',
'brave',
'opera',
]
const commonPlatforms = [
// browser extensions
...browserPlatforms,
]
// browser reload
gulp.task('dev:reload', function () {
livereload.listen({
port: 35729,
})
})
// copy universal
const copyTaskNames = []
const copyDevTaskNames = []
createCopyTasks('locales', {
source: './app/_locales/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}/_locales`),
})
createCopyTasks('images', {
source: './app/images/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}/images`),
})
createCopyTasks('contractImages', {
source: './node_modules/eth-contract-metadata/images/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}/images/contract`),
})
createCopyTasks('fonts', {
source: './app/fonts/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}/fonts`),
})
createCopyTasks('vendor', {
source: './app/vendor/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}/vendor`),
})
createCopyTasks('css', {
source: './ui/app/css/output/',
destinations: commonPlatforms.map((platform) => `./dist/${platform}`),
})
createCopyTasks('reload', {
devOnly: true,
source: './app/scripts/',
pattern: '/chromereload.js',
destinations: commonPlatforms.map((platform) => `./dist/${platform}`),
})
createCopyTasks('html', {
source: './app/',
pattern: '/*.html',
destinations: commonPlatforms.map((platform) => `./dist/${platform}`),
})
// copy extension
createCopyTasks('manifest', {
source: './app/',
pattern: '/*.json',
destinations: browserPlatforms.map((platform) => `./dist/${platform}`),
})
function createCopyTasks (label, opts) {
if (!opts.devOnly) {
const copyTaskName = `copy:${label}`
copyTask(copyTaskName, opts)
copyTaskNames.push(copyTaskName)
}
const copyDevTaskName = `dev:copy:${label}`
copyTask(copyDevTaskName, Object.assign({ devMode: true }, opts))
copyDevTaskNames.push(copyDevTaskName)
}
function copyTask (taskName, opts) {
const source = opts.source
const destination = opts.destination
const destinations = opts.destinations || [destination]
const pattern = opts.pattern || '/**/*'
const devMode = opts.devMode
return gulp.task(taskName, function () {
if (devMode) {
watch(source + pattern, (event) => {
livereload.changed(event.path)
performCopy()
})
}
return performCopy()
})
function performCopy () {
// stream from source
let stream = gulp.src(source + pattern, { base: source })
// copy to destinations
destinations.forEach(function (destination) {
stream = stream.pipe(gulp.dest(destination))
})
return stream
}
}
// manifest tinkering
gulp.task('manifest:chrome', function () {
return gulp.src('./dist/chrome/manifest.json')
.pipe(jsoneditor(function (json) {
delete json.applications
json.minimum_chrome_version = '58'
return json
}))
.pipe(gulp.dest('./dist/chrome', { overwrite: true }))
})
gulp.task('manifest:opera', function () {
return gulp.src('./dist/opera/manifest.json')
.pipe(jsoneditor(function (json) {
json.permissions = [
'storage',
'tabs',
'clipboardWrite',
'clipboardRead',
'http://localhost:8545/',
]
return json
}))
.pipe(gulp.dest('./dist/opera', { overwrite: true }))
})
gulp.task('manifest:production', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
'./dist/brave/manifest.json',
'./dist/opera/manifest.json',
], { base: './dist/' })
// Exclude chromereload script in production:
.pipe(jsoneditor(function (json) {
json.background.scripts = json.background.scripts.filter((script) => {
return !script.includes('chromereload')
})
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
gulp.task('manifest:testing', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
], { base: './dist/' })
// Exclude chromereload script in production:
.pipe(jsoneditor(function (json) {
json.permissions = [...json.permissions, 'webRequestBlocking', 'http://localhost/*']
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
const scriptsToExcludeFromBackgroundDevBuild = {
'bg-libs.js': true,
}
gulp.task('manifest:testing-local', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
], { base: './dist/' })
.pipe(jsoneditor(function (json) {
json.background = {
...json.background,
scripts: json.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName]),
}
json.permissions = [...json.permissions, 'webRequestBlocking', 'http://localhost/*']
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
gulp.task('manifest:dev', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
], { base: './dist/' })
.pipe(jsoneditor(function (json) {
json.background = {
...json.background,
scripts: json.background.scripts.filter((scriptName) => !scriptsToExcludeFromBackgroundDevBuild[scriptName]),
}
json.permissions = [...json.permissions, 'webRequestBlocking']
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
gulp.task('optimize:images', function () {
return gulp.src('./dist/**/images/**', { base: './dist/' })
.pipe(imagemin())
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
gulp.task('copy',
gulp.series(
gulp.parallel(...copyTaskNames),
'manifest:production',
'manifest:chrome',
'manifest:opera'
)
)
gulp.task('dev:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:dev',
'manifest:chrome',
'manifest:opera'
)
)
gulp.task('test:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:chrome',
'manifest:opera',
'manifest:testing-local'
)
)
// scss compilation and autoprefixing tasks
gulp.task('build:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: false,
}))
gulp.task('dev:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: true,
pattern: 'ui/app/**/*.scss',
}))
function createScssBuildTask ({ src, dest, devMode, pattern }) {
return function () {
if (devMode) {
watch(pattern, async (event) => {
const stream = buildScss()
await endOfStream(stream)
livereload.changed(event.path)
})
return buildScssWithSourceMaps()
}
return buildScss()
}
function buildScssWithSourceMaps () {
return gulp.src(src)
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
.pipe(rtlcss())
.pipe(rename({ suffix: '-rtl' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest(dest))
}
function buildScss () {
return gulp.src(src)
.pipe(sass().on('error', sass.logError))
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
.pipe(rtlcss())
.pipe(rename({ suffix: '-rtl' }))
.pipe(gulp.dest(dest))
}
}
gulp.task('lint-scss', function () {
return gulp
.src('ui/app/css/itcss/**/*.scss')
.pipe(gulpStylelint({
reporters: [
{ formatter: 'string', console: true },
],
fix: true,
}))
})
// build js
const buildJsFiles = [
'inpage',
'contentscript',
'background',
'ui',
'phishing-detect',
]
// bundle tasks
createTasksForBuildJsDeps({ filename: 'bg-libs', key: 'background' })
createTasksForBuildJsDeps({ filename: 'ui-libs', key: 'ui' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:test-extension:js', devMode: true, testing: 'true' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:test:extension:js', testing: 'true' })
function createTasksForBuildJsDeps ({ key, filename }) {
const destinations = browserPlatforms.map((platform) => `./dist/${platform}`)
const bundleTaskOpts = Object.assign({
buildSourceMaps: true,
sourceMapDir: '../sourcemaps',
minifyBuild: true,
devMode: false,
})
gulp.task(`build:extension:js:deps:${key}`, bundleTask(Object.assign({
label: filename,
filename: `${filename}.js`,
destinations,
buildLib: true,
dependenciesToBundle: externalDependenciesMap[key],
}, bundleTaskOpts)))
}
function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, testing, bundleTaskOpts = {} }) {
// inpage must be built before all other scripts:
const rootDir = './app/scripts'
const nonInpageFiles = buildJsFiles.filter((file) => file !== 'inpage')
const buildPhase1 = ['inpage']
const buildPhase2 = nonInpageFiles
const destinations = browserPlatforms.map((platform) => `./dist/${platform}`)
bundleTaskOpts = Object.assign({
buildSourceMaps: true,
sourceMapDir: '../sourcemaps',
minifyBuild: !devMode,
buildWithFullPaths: devMode,
watch: devMode,
devMode,
testing,
}, bundleTaskOpts)
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
}
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
// bundle task for each file
const jsFiles = [].concat(buildPhase1, buildPhase2)
jsFiles.forEach((jsFile) => {
gulp.task(`${taskPrefix}:${jsFile}`, bundleTask(Object.assign({
label: jsFile,
filename: `${jsFile}.js`,
filepath: `${rootDir}/${jsFile}.js`,
externalDependencies: bundleTaskOpts.devMode ? undefined : externalDependenciesMap[jsFile],
destinations,
}, bundleTaskOpts)))
})
// compose into larger task
const subtasks = []
subtasks.push(gulp.parallel(buildPhase1.map((file) => `${taskPrefix}:${file}`)))
if (buildPhase2.length) {
subtasks.push(gulp.parallel(buildPhase2.map((file) => `${taskPrefix}:${file}`)))
}
gulp.task(taskPrefix, gulp.series(subtasks))
}
// clean dist
gulp.task('clean', function clean () {
return del(['./dist/*'])
})
// zip tasks for distribution
gulp.task('zip:chrome', zipTask('chrome'))
gulp.task('zip:firefox', zipTask('firefox'))
gulp.task('zip:opera', zipTask('opera'))
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:opera'))
// high level tasks
gulp.task('dev:test',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:test-extension:js',
'test:copy',
'dev:reload'
)
)
)
gulp.task('dev:extension',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:copy',
'dev:reload'
)
)
)
gulp.task('build',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js:deps:background',
'build:extension:js:deps:ui',
'build:extension:js',
'copy'
),
'optimize:images'
)
)
gulp.task('build:test',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js:deps:background',
'build:extension:js:deps:ui',
'build:test:extension:js',
'copy'
),
'manifest:testing'
)
)
gulp.task('dist',
gulp.series(
'build',
'zip'
)
)
// task generators
function zipTask (target) {
return () => {
return gulp.src(`dist/${target}/**`)
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
.pipe(gulp.dest('builds'))
}
}
function generateBundler (opts, performBundle) {
const browserifyOpts = assign({}, watchify.args, {
plugin: [],
transform: [],
debug: opts.buildSourceMaps,
fullPaths: opts.buildWithFullPaths,
})
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'
}
// Inject variables into bundle
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment,
NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
}), {
global: true,
})
if (opts.watch) {
bundler = watchify(bundler)
// on any file update, re-runs the bundler
bundler.on('update', async (ids) => {
const stream = performBundle()
await endOfStream(stream)
livereload.changed(`${ids}`)
})
}
return bundler
}
function bundleTask (opts) {
let bundler
return performBundle
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.watch) {
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
if (opts.buildSourceMaps) {
buildStream = buildStream
// loads map from browserify file
.pipe(sourcemaps.init({ loadMaps: true }))
}
// Minification
if (opts.minifyBuild) {
buildStream = buildStream
.pipe(terser({
mangle: {
reserved: [ 'MetamaskInpageProvider' ],
},
}))
}
// Finalize Source Maps
if (opts.buildSourceMaps) {
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(opts.sourceMapDir))
}
}
// write completed bundles
opts.destinations.forEach((dest) => {
buildStream = buildStream.pipe(gulp.dest(dest))
})
return 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 beep () {
process.stdout.write('\x07')
}

View File

@ -3,12 +3,13 @@
"version": "0.0.0",
"private": true,
"scripts": {
"start": "gulp dev:extension",
"dist": "gulp dist",
"start:test": "gulp dev:test",
"start": "yarn build dev",
"dist": "yarn build prod",
"build": "node development/build/index.js",
"start:test": "yarn build testDev",
"benchmark:chrome": "SELENIUM_BROWSER=chrome node test/e2e/benchmark.js",
"benchmark:firefox": "SELENIUM_BROWSER=firefox node test/e2e/benchmark.js",
"build:test": "gulp build:test",
"build:test": "yarn build test",
"test": "yarn test:unit && yarn lint",
"dapp": "node development/static-server.js test/e2e/contract-test --port 8080",
"dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && node development/static-server.js test/e2e/contract-test --port 8080'",
@ -19,7 +20,7 @@
"test:unit": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
"test:unit:global": "mocha --exit --require test/env.js --require test/setup.js --recursive mocha test/unit-global/*",
"test:integration": "yarn test:integration:build && yarn test:flat",
"test:integration:build": "gulp build:scss",
"test:integration:build": "yarn build styles",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-web3.sh",
@ -193,7 +194,7 @@
"browserify-derequire": "^1.0.1",
"browserify-transform-tools": "^1.7.0",
"chai": "^4.1.0",
"chalk": "^2.4.2",
"chalk": "^3.0.0",
"chromedriver": "^79.0.0",
"concurrently": "^5.1.0",
"coveralls": "^3.0.0",
@ -210,8 +211,10 @@
"eslint-plugin-mocha": "^6.2.2",
"eslint-plugin-react": "^7.18.3",
"fancy-log": "^1.3.3",
"fast-glob": "^3.2.2",
"fetch-mock": "^6.5.2",
"file-loader": "^1.1.11",
"fs-extra": "^8.1.0",
"ganache-cli": "^6.4.4",
"ganache-core": "2.8.0",
"geckodriver": "^1.19.1",
@ -221,7 +224,6 @@
"gulp-babel": "^8.0.0",
"gulp-debug": "^3.2.0",
"gulp-imagemin": "^6.1.0",
"gulp-json-editor": "^2.5.4",
"gulp-livereload": "4.0.0",
"gulp-multi-process": "^1.3.1",
"gulp-rename": "^1.4.0",
@ -252,6 +254,7 @@
"proxyquire": "^2.1.3",
"qs": "^6.2.0",
"qunitjs": "^2.4.1",
"randomcolor": "^0.5.4",
"react-devtools": "^4.4.0",
"react-test-renderer": "^16.12.0",
"read-installed": "^4.0.3",

View File

@ -1,4 +1,4 @@
const fs = require('fs').promises
const { promises: fs } = require('fs')
const Koa = require('koa')
const path = require('path')

117
yarn.lock
View File

@ -1888,11 +1888,24 @@
"@nodelib/fs.stat" "2.0.2"
run-parallel "^1.1.9"
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==
dependencies:
"@nodelib/fs.stat" "2.0.3"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.2", "@nodelib/fs.stat@^2.0.1":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz#2762aea8fe78ea256860182dcb52d61ee4b8fda6"
integrity sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==
"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
"@nodelib/fs.stat@^1.1.2":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
@ -1906,6 +1919,14 @@
"@nodelib/fs.scandir" "2.1.2"
fastq "^1.6.0"
"@nodelib/fs.walk@^1.2.3":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==
dependencies:
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
@ -7265,7 +7286,7 @@ concurrently@^5.1.0:
tree-kill "^1.2.2"
yargs "^13.3.0"
config-chain@^1.1.11, config-chain@^1.1.12:
config-chain@^1.1.11:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
@ -8484,11 +8505,6 @@ deepmerge@^2.0.1:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
deepmerge@^4.2.1:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
default-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
@ -8739,11 +8755,6 @@ detect-indent@^4.0.0:
dependencies:
repeating "^2.0.0"
detect-indent@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==
detect-libc@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@ -9255,16 +9266,6 @@ editions@^1.3.3:
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==
editorconfig@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
dependencies:
commander "^2.19.0"
lru-cache "^4.1.5"
semver "^5.6.0"
sigmund "^1.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -11534,6 +11535,18 @@ fast-glob@^3.0.3:
merge2 "^1.2.3"
micromatch "^4.0.2"
fast-glob@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d"
integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.0"
merge2 "^1.3.0"
micromatch "^4.0.2"
picomatch "^2.2.1"
fast-json-parse@^1.0.0, fast-json-parse@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d"
@ -12306,6 +12319,15 @@ fs-extra@^8.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-minipass@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
@ -12762,7 +12784,7 @@ glob-parent@^3.0.1, glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-parent@^5.0.0, glob-parent@~5.1.0:
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
@ -13158,7 +13180,7 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.15:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
graceful-fs@^4.2.2:
graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
@ -13299,17 +13321,6 @@ gulp-imagemin@^6.1.0:
imagemin-optipng "^7.0.0"
imagemin-svgo "^7.0.0"
gulp-json-editor@^2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.5.4.tgz#b77b46bca22d2dd1ac9f15bbec1eddbe5ef3567c"
integrity sha512-3IdMYsSACfLFYipet9Rmpag7PEU059KnR6TWgfuAfz+ftyzN8yaEvf9vXAD5b9K9v711Ymcpqe6vWGQYfQJ/uQ==
dependencies:
deepmerge "^4.2.1"
detect-indent "^6.0.0"
js-beautify "^1.10.2"
plugin-error "^1.0.1"
through2 "^3.0.1"
gulp-livereload@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/gulp-livereload/-/gulp-livereload-4.0.0.tgz#be4a6b01731a93f76f4fb29c9e62e323affe7d03"
@ -16412,17 +16423,6 @@ js-base64@^2.1.8:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
integrity sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==
js-beautify@^1.10.2:
version "1.10.3"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.3.tgz#c73fa10cf69d3dfa52d8ed624f23c64c0a6a94c1"
integrity sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==
dependencies:
config-chain "^1.1.12"
editorconfig "^0.15.3"
glob "^7.1.3"
mkdirp "~0.5.1"
nopt "~4.0.1"
js-levenshtein@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
@ -18533,14 +18533,6 @@ lru-cache@^3.2.0:
dependencies:
pseudomap "^1.0.1"
lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@ -18916,6 +18908,11 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
merge2@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
merkle-lib@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326"
@ -20097,7 +20094,7 @@ nonce-tracker@^1.0.0:
dependencies:
abbrev "1"
nopt@^4.0.1, nopt@~4.0.1:
nopt@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
@ -21682,7 +21679,7 @@ phantomjs-prebuilt@^2.1.16, phantomjs-prebuilt@~2.1.7:
request-progress "^2.0.1"
which "^1.2.10"
picomatch@^2.0.4, picomatch@^2.0.5:
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
@ -23005,6 +23002,11 @@ randombytes@^2.0.1, randombytes@^2.0.3, randombytes@^2.0.6:
dependencies:
safe-buffer "^5.1.0"
randomcolor@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.5.4.tgz#df615b13f25b89ea58c5f8f72647f0a6f07adcc3"
integrity sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA==
randomfill@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62"
@ -25419,11 +25421,6 @@ side-channel@^1.0.2:
es-abstract "^1.17.0-next.1"
object-inspect "^1.7.0"
sigmund@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"