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>feature/default_network_editable
parent
2df8b85c5f
commit
7686edadb0
@ -0,0 +1 @@ |
||||
{} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"minimum_chrome_version": "58" |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"applications": { |
||||
"gecko": { |
||||
"id": "webextension@metamask.io", |
||||
"strict_min_version": "56.0" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
{ |
||||
"permissions": [ |
||||
"storage", |
||||
"tabs", |
||||
"clipboardWrite", |
||||
"clipboardRead", |
||||
"http://localhost:8545/" |
||||
] |
||||
} |
@ -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) |
||||
|
@ -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') |
||||
} |
||||
} |
@ -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'), |
||||
) |
||||
} |
||||
} |
@ -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) |
||||
|
||||
} |
@ -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)) |
||||
} |
@ -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') |
||||
} |
@ -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}`) |
||||
})) |
||||
} |
||||
|
||||
} |
@ -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)) |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -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())) |
||||
} |
||||
} |
@ -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') |
||||
} |
Loading…
Reference in new issue