1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00
metamask-extension/gulpfile.js
Mark Stacey 7357f011c3
Set minimum browser version in manifest (#6877)
Set the minimum browser version supported in the extension manifest.
Currently we only ship the extension on Chrome and Firefox, so the
minimum version has been set for those two browsers.

Relates to #6805
2019-07-18 18:16:30 -03:00

602 lines
15 KiB
JavaScript

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 gutil = require('gulp-util')
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.assign')
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 stylefmt = require('gulp-stylefmt')
const uglify = require('gulp-uglify-es').default
const pify = require('pify')
const gulpMultiProcess = require('gulp-multi-process')
const endOfStream = pify(require('end-of-stream'))
const packageJSON = require('./package.json')
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 uiDependenciesToBundle = [
...materialUIDependencies,
...reactDepenendencies,
...d3Dependencies,
]
function gulpParallel (...args) {
return function spawnGulpChildProcess (cb) {
return gulpMultiProcess(args, cb, true)
}
}
const browserPlatforms = [
'firefox',
'chrome',
'brave',
'edge',
'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('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/edge/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']
return json
}))
.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:chrome',
'manifest:opera'
)
)
gulp.task('test:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:chrome',
'manifest:opera',
'manifest:testing'
)
)
// 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))
}
function buildScss () {
return gulp.src(src)
.pipe(sass().on('error', sass.logError))
.pipe(autoprefixer())
.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,
}))
})
gulp.task('fmt-scss', function () {
return gulp.src('ui/app/css/itcss/**/*.scss')
.pipe(stylefmt())
.pipe(gulp.dest('ui/app/css/itcss'))
})
// build js
const buildJsFiles = [
'inpage',
'contentscript',
'background',
'ui',
'phishing-detect',
]
// bundle tasks
createTasksForBuildJsUIDeps({ dependenciesToBundle: uiDependenciesToBundle, filename: 'libs' })
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 createTasksForBuildJsUIDeps ({ 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:uideps', bundleTask(Object.assign({
label: filename,
filename: `${filename}.js`,
destinations,
buildLib: true,
dependenciesToBundle: uiDependenciesToBundle,
}, 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: jsFile === 'ui' && !bundleTaskOpts.devMode && uiDependenciesToBundle,
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:edge', zipTask('edge'))
gulp.task('zip:opera', zipTask('opera'))
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
// high level tasks
gulp.task('dev',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:copy',
'dev:reload'
)
)
)
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:uideps',
'build:extension:js',
'copy'
)
)
)
gulp.task('build:test',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js:uideps',
'build:test:extension:js',
'copy'
),
'manifest:testing'
)
)
gulp.task('build:extension',
gulp.series(
'clean',
'build:scss',
gulp.parallel(
'build:extension:js',
'copy'
)
)
)
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: 'browserify-derequire',
debug: opts.buildSourceMaps,
fullPaths: opts.buildWithFullPaths,
})
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')
.transform('brfs')
if (opts.buildLib) {
bundler = bundler.require(opts.dependenciesToBundle)
}
if (opts.externalDependencies) {
bundler = bundler.external(opts.externalDependencies)
}
// inject variables into bundle
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
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) {
const bundler = generateBundler(opts, performBundle)
// output build logs to terminal
bundler.on('log', gutil.log)
return performBundle
function performBundle () {
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(uglify({
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 beep () {
process.stdout.write('\x07')
}