diff --git a/lib/rust/build.rs b/lib/rust/build.rs index ec0e49e0e..882bcfdf6 100755 --- a/lib/rust/build.rs +++ b/lib/rust/build.rs @@ -3,11 +3,8 @@ extern crate includedir_codegen; use includedir_codegen::Compression; use std::env; -static CARGOENV: &str = "cargo:rustc-env="; - fn main() { - let dist_path = format!("{}/../../../../{}", env::var("OUT_DIR").unwrap(), "compiled-web"); - println!("{}TAURI_DIST_DIR={}", CARGOENV, dist_path); + let dist_path = env::var("TAURI_DIST_DIR").unwrap(); includedir_codegen::start("ASSETS") .dir(dist_path, Compression::None) .build("data.rs") diff --git a/lib/tauri.js b/lib/tauri.js new file mode 100644 index 000000000..131acb117 --- /dev/null +++ b/lib/tauri.js @@ -0,0 +1,337 @@ +<% + if (typeof(confName) === 'undefined') { + confName = 'quasar.conf.js' + } +%> +/** + * THIS FILE IS GENERATED AUTOMATICALLY. + * DO NOT EDIT. + * + * Please whitelist these API functions in <% confName %> + * + **/ + +/** + * @module tauri + * @description This API interface makes powerful interactions available + * to be run on client side applications. They are opt-in features, and + * must be enabled in <% confName %> + * + * Each binding MUST provide these interfaces in order to be compliant, + * and also whitelist them based upon the developer's settings. + */ + +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1) +} + +const uid = function () { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4() +} + +<% if (ctx.dev) { %> +/** + * @name __whitelistWarning + * @description Present a stylish warning to the developer that their API + * call has not been whitelisted in <% confName %> + * @param {String} func - function name to warn + * @private + */ +const __whitelistWarning = function (func) { + console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to <% confName %>: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://quasar.dev/quasar-cli/creating-tauri-apps/api#' + func , 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ') +} +<% } %> + +/** + * @name __reject + * @description is a private promise used to deflect un-whitelisted tauri API calls + * Its only purpose is to maintain thenable structure in client code without + * breaking the application + * * @type {Promise} + * @private + */ +const __reject = new Promise((reject) => { reject }) + +export default class Tauri { +<% if (ctx.dev) { %> + /** + * @name invoke + * @description Calls a Quasar Core feature, such as setTitle + * @param {Object} args + */ +<% } %> + static invoke (args) { + Object.freeze(args) + external.invoke(JSON.stringify(args)) + } + +<% if (ctx.dev) { %> + /** + * @name addEventListener + * @description Add an event listener to Tauri back end + * @param {String} event + * @param {Function} handler + * @param {Boolean} once + */ +<% } %> + static addEventListener(event, handler, once = false) { + this.invoke({ + cmd: 'addEventListener', + event, + handler: this.transformCallback(handler, once), + once + }) + } + +<% if (ctx.dev) { %> + /** + * @name emit + * @description Emits an event to the Tauri back end + * @param {String} event + * @param {Object} payload + */ +<% } %> + static emit(event, payload) { + this.invoke({ + cmd: 'emit', + event, + payload + }) +} + +<% if (ctx.dev) { %> + /** + * @name transformCallback + * @description Registers a callback with a uid + * @param {Function} callback + * @param {Boolean} once + * @returns {*} + */ +<% } %> + static transformCallback (callback, once = true) { + const identifier = Object.freeze(uid()) + window[identifier] = (result) => { + if (once) { + delete window[identifier] + } + return callback && callback(result) + } + return identifier + } + +<% if (ctx.dev) { %> + /** + * @name promisified + * @description Turns a request into a chainable promise + * @param {Object} args + * @returns {Promise} + */ +<% } %> + static promisified (args) { + return new Promise((resolve, reject) => { + this.invoke({ + callback: this.transformCallback(resolve), + error: this.transformCallback(reject), + ...args + }) + }) + } + +<% if (ctx.dev) { %> + /** + * @name readTextFile + * @description Accesses a non-binary file on the user's filesystem + * and returns the content. Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ +<% } %> + static readTextFile (path) { + <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %> + Object.freeze(path) + return this.promisified({ cmd: 'readTextFile', path }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('readTextFile') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name readBinaryFile + * @description Accesses a binary file on the user's filesystem + * and returns the content. Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ +<% } %> + static readBinaryFile (path) { + <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %> + Object.freeze(path) + return this.promisified({ cmd: 'readBinaryFile', path }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('readBinaryFile') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name writeFile + * @description Write a file to the Local Filesystem. + * Permissions based on the app's PID owner + * @param {Object} cfg + * @param {String} cfg.file + * @param {String|Binary} cfg.contents + */ +<% } %> + static writeFile (cfg) { + Object.freeze(cfg) + <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %> + this.invoke({ cmd: 'writeFile', file: cfg.file, contents: cfg.contents }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('writeFile') + <% } %> + return __reject + <% } %> } + +<% if (ctx.dev) { %> + /** + * @name listFiles + * @description Get the files in a path. + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ +<% } %> + static listFiles (path) { + <% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %> + Object.freeze(path) + return this.promisified({ cmd: 'listFiles', path }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('listFiles') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name listDirs + * @description Get the directories in a path. + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ +<% } %> + static listDirs (path) { + <% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %> + Object.freeze(path) + return this.promisified({ cmd: 'listDirs', path }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('listDirs') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name setTitle + * @description Set the application's title + * @param {String} title + */ +<% } %> + static setTitle (title) { + <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %> + Object.freeze(title) + this.invoke({ cmd: 'setTitle', title }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('setTitle') + <% } %> + return __reject + <% } %> + } + + <% if (ctx.dev) { %> + /** + * @name open + * @description Open an URI + * @param {String} uri + */ +<% } %> + static open (uri) { + <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %> + Object.freeze(uri) + this.invoke({ cmd: 'open', uri }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('open') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name execute + * @description Execute a program with arguments. + * Permissions based on the app's PID owner + * @param {String} command + * @param {String|Array} args + * @returns {*|Promise|Promise} + */ +<% } %> + static execute (command, args) { + <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %> + Object.freeze(command) + if (typeof args === 'string' || typeof args === 'object') { + Object.freeze(args) + } + return this.promisified({ cmd: 'execute', command, args: typeof (args) === 'string' ? [args] : args }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('execute') + <% } %> + return __reject + <% } %> + } + +<% if (ctx.dev) { %> + /** + * @name bridge + * @description Securely pass a message to the backend. + * @example + * this.$q.tauri.bridge('QBP/1/ping/client-1', 'pingback') + * @param {String} command - a compressed, slash-delimited and + * versioned API call to the backend. + * @param {String|Object}payload + * @returns {*|Promise|Promise} + */ +<% } %> + static bridge (command, payload) { +<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %> + Object.freeze(command) + if (typeof payload === 'string' || typeof payload === 'object') { + Object.freeze(payload) + } + return this.promisified({ cmd: 'bridge', command, payload: typeof (payload) === 'object' ? [payload] : payload }) +<% } else { %> +<% if (ctx.dev) { %> + __whitelistWarning('bridge') +<% } %> + return __reject +<% } %> + } +} diff --git a/package.json b/package.json index 1689b9a75..e34fde274 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,10 @@ "name": "@quasar/tauri", "version": "1.0.0-alpha.1", "description": "Multi-binding collection of libraries and templates for building Tauri", - "main": "templates/index.js", + "main": "tauri/lib.js", + "bin": { + "tauri": "tauri/bin/tauri.js" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -23,5 +26,18 @@ "node": ">= 10.16.0", "npm": ">= 6.6.0", "yarn": ">= 1.17.3" + }, + "dependencies": { + "@iarna/toml": "^2.2.3", + "chalk": "^2.4.2", + "chokidar": "^3.0.2", + "cross-spawn": "^6.0.5", + "fast-glob": "^3.0.4", + "fs-extra": "^8.1.0", + "lodash.debounce": "^4.0.8", + "lodash.template": "^4.5.0", + "minimist": "^1.2.0", + "ms": "^2.1.2", + "webpack-merge": "^4.2.1" } } diff --git a/tauri/bin/tauri-build.js b/tauri/bin/tauri-build.js new file mode 100644 index 000000000..9ca86ffcb --- /dev/null +++ b/tauri/bin/tauri-build.js @@ -0,0 +1,42 @@ +const + parseArgs = require('minimist'), + { writeFileSync } = require('fs-extra'), + path = require('path') + +const argv = parseArgs(process.argv.slice(2), { + alias: { + h: 'help', + d: 'debug' + }, + boolean: ['h', 'd'] +}) + +if (argv.help) { + console.log(` + Description + Tauri build. + Usage + $ tauri build + Options + --help, -h Displays this message + `) + process.exit(0) +} + +const appPaths = require('../helpers/app-paths'), + Runner = require('../runner'), + Injector = require('../injector'), + tauri = new Runner(appPaths), + injector = new Injector(appPaths), + tauriConfig = require('../helpers/tauri-config')({ + ctx: { + debug: argv.debug + } + }) +const {bundle, ...cfg} = tauriConfig.tauri, + cfgDir = injector.configDir() +writeFileSync(path.join(cfgDir, 'config.json'), JSON.stringify(cfg)) +writeFileSync(path.join(cfgDir, 'bundle.json'), JSON.stringify(bundle)) + +require('../helpers/generator')(tauriConfig) +tauri.build(tauriConfig) diff --git a/tauri/bin/tauri-dev.js b/tauri/bin/tauri-dev.js new file mode 100644 index 000000000..aeaf4f13f --- /dev/null +++ b/tauri/bin/tauri-dev.js @@ -0,0 +1,44 @@ +const + parseArgs = require('minimist'), + path = require('path'), + { writeFileSync } = require('fs-extra') + +const argv = parseArgs(process.argv.slice(2), { + alias: { + h: 'help' + }, + boolean: ['h'] +}) + +if (argv.help) { + console.log(` + Description + Tauri dev. + Usage + $ tauri dev + Options + --help, -h Displays this message + `) + process.exit(0) +} + +const appPaths = require('../helpers/app-paths'), + Runner = require('../runner'), + Injector = require('../injector'), + tauri = new Runner(appPaths), + injector = new Injector(appPaths), + tauriConfig = require('../helpers/tauri-config')({ + ctx: { + debug: true + } + }) + +const { bundle, ...cfg } = tauriConfig.tauri, + cfgDir = injector.configDir() + +writeFileSync(path.join(cfgDir, 'config.json'), JSON.stringify(cfg)) +writeFileSync(path.join(cfgDir, 'bundle.json'), JSON.stringify(bundle)) + +require('../helpers/generator')(tauriConfig) + +tauri.run(tauriConfig) diff --git a/tauri/bin/tauri-init.js b/tauri/bin/tauri-init.js new file mode 100644 index 000000000..509cdd043 --- /dev/null +++ b/tauri/bin/tauri-init.js @@ -0,0 +1,29 @@ +const + parseArgs = require('minimist'), + appPaths = require('../helpers/app-paths'), + log = require('../helpers/logger')('app:tauri') + +const argv = parseArgs(process.argv.slice(2), { + alias: { + h: 'help' + }, + boolean: ['h'] +}) + +if (argv.help) { + console.log(` + Description + Inits the Tauri template. + Usage + $ tauri init + Options + --help, -h Displays this message + `) + process.exit(0) +} + +const Injector = require('../injector'), + injector = new Injector(appPaths) +if (injector.injectTemplate()) { + log('Tauri template successfully installed') +} diff --git a/tauri/bin/tauri.js b/tauri/bin/tauri.js new file mode 100755 index 000000000..3346c9f75 --- /dev/null +++ b/tauri/bin/tauri.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +const cmds = ['init', 'dev', 'build', 'help'] + +const cmd = process.argv[2] +if (!cmd || cmd === '-h' || cmd === '--help' || cmd === 'help') { + console.log(` + Description + Tauri CLI. + Usage + $ tauri ${cmds.join('|')} + Options + --help, -h Displays this message + `) + process.exit(0) +} +if (cmds.includes(cmd)) { + process.argv.splice(2, 1) + require(`./tauri-${cmd}`) +} else { + console.log(`Invalid command ${cmd}. Use one of ${cmds.join(',')}.`) +} diff --git a/tauri/helpers/app-paths.js b/tauri/helpers/app-paths.js new file mode 100644 index 000000000..e2c048045 --- /dev/null +++ b/tauri/helpers/app-paths.js @@ -0,0 +1,38 @@ +const + { existsSync } = require('fs'), + path = require('path'), + resolve = path.resolve, + join = path.join + +function getAppDir() { + let dir = process.cwd() + + while (dir.length && dir[dir.length - 1] !== path.sep) { + if (existsSync(join(dir, 'tauri.conf.js'))) { + return dir + } + + dir = path.normalize(join(dir, '..')) + } + + const + logger = require('./logger') + warn = logger('app:paths', 'red') + + warn(`⚠️ Error. This command must be executed inside a Tauri project folder.`) + warn() + process.exit(1) +} + +const appDir = getAppDir(), + tauriDir = resolve(appDir, 'src-tauri') + +module.exports = { + appDir, + tauriDir, + + resolve: { + app: dir => join(appDir, dir), + tauri: dir => join(tauriDir, dir) + } +} diff --git a/tauri/helpers/generator.js b/tauri/helpers/generator.js new file mode 100644 index 000000000..115b2cf3e --- /dev/null +++ b/tauri/helpers/generator.js @@ -0,0 +1,13 @@ +const compileTemplate = require('lodash.template'), + { readFileSync, writeFileSync } = require('fs'), + appPaths = require('./app-paths'), + path = require('path') + +module.exports = cfg => { + const apiTemplate = readFileSync(path.resolve(__dirname, '../../lib/tauri.js'), 'utf-8') + const apiContent = compileTemplate(apiTemplate)({ + ...cfg, + confName: 'tauri.conf.js' + }) + writeFileSync(appPaths.resolve.tauri('tauri.js'), apiContent, 'utf-8') +} diff --git a/tauri/helpers/logger.js b/tauri/helpers/logger.js new file mode 100644 index 000000000..f769cf0d2 --- /dev/null +++ b/tauri/helpers/logger.js @@ -0,0 +1,21 @@ +const + ms = require('ms'), + chalk = require('chalk') + +let prevTime + +module.exports = function (banner, color = 'green') { + return function (msg) { + const + curr = +new Date(), + diff = curr - (prevTime || curr) + + prevTime = curr + + if (msg) { + console.log(` ${chalk[color](banner)} ${msg} ${chalk.green(`+${ms(diff)}`)}`) + } else { + console.log() + } + } +} diff --git a/tauri/helpers/on-shutdown.js b/tauri/helpers/on-shutdown.js new file mode 100644 index 000000000..d8f627c21 --- /dev/null +++ b/tauri/helpers/on-shutdown.js @@ -0,0 +1,15 @@ +module.exports = function (fn) { + const cleanup = () => { + try { + fn() + } finally { + process.exit() + } + } + + process.on('exit', cleanup) + process.on('SIGINT', cleanup) + process.on('SIGTERM', cleanup) + process.on('SIGHUP', cleanup) + process.on('SIGBREAK', cleanup) +} diff --git a/tauri/helpers/spawn.js b/tauri/helpers/spawn.js new file mode 100644 index 000000000..8c604cfd9 --- /dev/null +++ b/tauri/helpers/spawn.js @@ -0,0 +1,62 @@ +const + logger = require('./logger'), + log = logger('app:spawn'), + warn = logger('app:spawn', 'red'), + crossSpawn = require('cross-spawn') + +/* + Returns pid, takes onClose + */ +module.exports.spawn = function (cmd, params, cwd, onClose) { + log(`Running "${cmd} ${params.join(' ')}"`) + log() + + const runner = crossSpawn( + cmd, + params, { + stdio: 'inherit', + stdout: 'inherit', + stderr: 'inherit', + cwd + } + ) + + runner.on('close', code => { + log() + if (code) { + log(`Command "${cmd}" failed with exit code: ${code}`) + } + + onClose && onClose(code) + }) + + return runner.pid +} + +/* + Returns nothing, takes onFail + */ +module.exports.spawnSync = function (cmd, params, cwd, onFail) { + log(`[sync] Running "${cmd} ${params.join(' ')}"`) + log() + + const runner = crossSpawn.sync( + cmd, + params, { + stdio: 'inherit', + stdout: 'inherit', + stderr: 'inherit', + cwd + } + ) + + if (runner.status || runner.error) { + warn() + warn(`⚠️ Command "${cmd}" failed with exit code: ${runner.status}`) + if (runner.status === null) { + warn(`⚠️ Please globally install "${cmd}"`) + } + onFail && onFail() + process.exit(1) + } +} diff --git a/tauri/helpers/tauri-config.js b/tauri/helpers/tauri-config.js new file mode 100644 index 000000000..ac269d2a2 --- /dev/null +++ b/tauri/helpers/tauri-config.js @@ -0,0 +1,29 @@ +const appPaths = require('./app-paths'), + merge = require('webpack-merge') + +module.exports = cfg => { + const tauriConf = require(appPaths.resolve.app('tauri.conf.js'))(cfg.ctx) + const config = merge({ + build: {}, + ctx: {}, + tauri: { + embeddedServer: { + active: true + }, + bundle: { + active: true + }, + whitelist: {}, + window: { + title: require(appPaths.resolve.app('package.json')).productName + }, + security: { + csp: 'default-src data: filesystem: ws: http: https: \'unsafe-eval\' \'unsafe-inline\'' + } + } + }, tauriConf, cfg) + + process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.distDir) + + return config +} diff --git a/tauri/injector.js b/tauri/injector.js new file mode 100644 index 000000000..ae679e8cf --- /dev/null +++ b/tauri/injector.js @@ -0,0 +1,41 @@ +const { copySync, renameSync, existsSync, mkdirSync } = require('fs-extra'), + path = require('path') + +class TauriInjector { + constructor(appPaths) { + this.appPaths = appPaths + } + + configDir() { + return path.resolve(__dirname, '..') + } + + injectTemplate() { + if (existsSync(this.appPaths.tauriDir)) { + console.log(`Tauri dir (${this.appPaths.tauriDir}) not empty.`) + return false + } + mkdirSync(this.appPaths.tauriDir) + copySync(path.resolve(__dirname, '../templates/rust'), this.appPaths.tauriDir) + const files = require('fast-glob').sync(['**/_*'], { + cwd: this.appPaths.tauriDir + }) + for (const rawPath of files) { + const targetRelativePath = rawPath.split('/').map(name => { + // dotfiles are ignored when published to npm, therefore in templates + // we need to use underscore instead (e.g. "_gitignore") + if (name.charAt(0) === '_' && name.charAt(1) !== '_') { + return `.${name.slice(1)}` + } + if (name.charAt(0) === '_' && name.charAt(1) === '_') { + return `${name.slice(1)}` + } + return name + }).join('/') + renameSync(this.appPaths.resolve.tauri(rawPath), this.appPaths.resolve.tauri(targetRelativePath)) + } + return true + } +} + +module.exports = TauriInjector diff --git a/tauri/lib.js b/tauri/lib.js new file mode 100644 index 000000000..9c6fd5faf --- /dev/null +++ b/tauri/lib.js @@ -0,0 +1,9 @@ +const TauriRunner = require('./runner'), + TauriInjector = require('./injector'), + path = require('path') + +module.exports = { + runner: TauriRunner, + injector: TauriInjector, + apiTemplatePath: path.resolve(__dirname, '../lib/tauri.js') +} diff --git a/tauri/runner.js b/tauri/runner.js new file mode 100644 index 000000000..4ff868cf8 --- /dev/null +++ b/tauri/runner.js @@ -0,0 +1,189 @@ +const + chokidar = require('chokidar'), + debounce = require('lodash.debounce') + +const + { spawn } = require('./helpers/spawn'), + log = require('./helpers/logger')('app:tauri') + onShutdown = require('./helpers/on-shutdown'), + { readFileSync, writeFileSync } = require('fs-extra') + +class TauriRunner { + constructor(appPaths) { + this.appPaths = appPaths + this.pid = 0 + this.tauriWatcher = null + + onShutdown(() => { + this.stop() + }) + } + + async run(cfg) { + const + url = cfg.build.APP_URL + + if (this.pid) { + if (this.url !== url) { + await this.stop() + } else { + return + } + } + + this.__manipulateToml(toml => { + this.__whitelistApi(cfg, toml) + }) + + this.url = url + + const args = ['--url', url] + + const startDevTauri = () => { + return this.__runCargoCommand({ + cargoArgs: ['run', '--features', 'dev'], + extraArgs: args + }) + } + + // Start watching for tauri app changes + this.tauriWatcher = chokidar + .watch([ + this.appPaths.resolve.tauri('src'), + this.appPaths.resolve.tauri('Cargo.toml'), + this.appPaths.resolve.tauri('build.rs') + ], { + watchers: { + chokidar: { + ignoreInitial: true + } + } + }) + .on('change', debounce(async () => { + await this.__stopCargo() + startDevTauri() + }, 1000)) + + return startDevTauri() + } + + async build(cfg) { + this.__manipulateToml(toml => { + this.__whitelistApi(cfg, toml) + }) + + const features = [] + if (cfg.tauri.embeddedServer.active) { + features.push('embedded-server') + } + + const buildFn = target => this.__runCargoCommand({ + cargoArgs: [cfg.tauri.bundle.active ? 'tauri-bundle' : 'build'] + .concat(features.length ? ['--features', ...features] : []) + .concat(cfg.ctx.debug ? [] : ['--release']) + .concat(target ? ['--target', target] : []) + }) + + if (cfg.ctx.debug || !cfg.ctx.targetName) { + // on debug mode or if not arget specified, + // build only for the current platform + return buildFn() + } + + const targets = cfg.ctx.target.split(',') + + for (const target of targets) { + await buildFn(target) + } + } + + stop() { + return new Promise((resolve, reject) => { + this.tauriWatcher && this.tauriWatcher.close() + this.__stopCargo().then(resolve) + }) + } + + __runCargoCommand({ + cargoArgs, + extraArgs + }) { + return new Promise(resolve => { + this.pid = spawn( + 'cargo', + + extraArgs ? + cargoArgs.concat(['--']).concat(extraArgs) : + cargoArgs, + + this.appPaths.tauriDir, + + code => { + if (code) { + warn() + warn(`⚠️ [FAIL] Cargo CLI has failed`) + warn() + process.exit(1) + } + + if (this.killPromise) { + this.killPromise() + this.killPromise = null + } else { // else it wasn't killed by us + warn() + warn('Cargo process was killed. Exiting...') + warn() + process.exit(0) + } + } + ) + + resolve() + }) + } + + __stopCargo() { + const pid = this.pid + + if (!pid) { + return Promise.resolve() + } + + log('Shutting down tauri process...') + this.pid = 0 + + return new Promise((resolve, reject) => { + this.killPromise = resolve + process.kill(pid) + }) + } + + __manipulateToml(callback) { + const toml = require('@iarna/toml'), + tomlPath = this.appPaths.resolve.tauri('Cargo.toml'), + tomlFile = readFileSync(tomlPath), + tomlContents = toml.parse(tomlFile) + + callback(tomlContents) + + const output = toml.stringify(tomlContents) + writeFileSync(tomlPath, output) + } + + __whitelistApi(cfg, tomlContents) { + if (!tomlContents.dependencies.tauri.features) { + tomlContents.dependencies.tauri.features = [] + } + + if (cfg.tauri.whitelist.all) { + if (!tomlContents.dependencies.tauri.features.includes('all-api')) { + tomlContents.dependencies.tauri.features.push('all-api') + } + } else { + const whitelist = Object.keys(cfg.tauri.whitelist).filter(w => cfg.tauri.whitelist[w] === true) + tomlContents.dependencies.tauri.features = whitelist.concat(tomlContents.dependencies.tauri.features.filter(f => f !== 'api' && cfg.tauri.whitelist[f] !== true)) + } + } +} + +module.exports = TauriRunner diff --git a/templates/rust/_gitignore b/templates/rust/_gitignore index 50c83018e..d4cf34321 100755 --- a/templates/rust/_gitignore +++ b/templates/rust/_gitignore @@ -7,4 +7,6 @@ Cargo.lock # These are backup files generated by rustfmt -**/*.rs.bk \ No newline at end of file +**/*.rs.bk + +tauri.js \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..37f2fa521 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,341 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@iarna/toml@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" + integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== + +"@nodelib/fs.scandir@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.1.tgz#7fa8fed654939e1a39753d286b48b4836d00e0eb" + integrity sha512-NT/skIZjgotDSiXs0WqYhgcuBKhUMgfekCmCGtkUAiLqZdOnrdjmZr9wRl3ll64J9NF79uZ4fk16Dx0yMc/Xbg== + dependencies: + "@nodelib/fs.stat" "2.0.1" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.1", "@nodelib/fs.stat@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.1.tgz#814f71b1167390cfcb6a6b3d9cdeb0951a192c14" + integrity sha512-+RqhBlLn6YRBGOIoVYthsG0J9dfpO79eJyN7BYBkZJtfqrBwf2KK+rD/M/yjZR6WBmIhAgOV7S60eCgaSWtbFw== + +"@nodelib/fs.walk@^1.2.1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.2.tgz#6a6450c5e17012abd81450eb74949a4d970d2807" + integrity sha512-J/DR3+W12uCzAJkw7niXDcqcKBg6+5G5Q/ZpThpGNzAUz70eOR6RV4XnnSN01qHZiVl0eavoxJsBypQoKsV2QQ== + dependencies: + "@nodelib/fs.scandir" "2.1.1" + fastq "^1.6.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09" + integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + +braces@^3.0.1, braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681" + integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA== + dependencies: + anymatch "^3.0.1" + braces "^3.0.2" + glob-parent "^5.0.0" + is-binary-path "^2.1.0" + is-glob "^4.0.1" + normalize-path "^3.0.0" + readdirp "^3.1.1" + optionalDependencies: + fsevents "^2.0.6" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +fast-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602" + integrity sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg== + dependencies: + "@nodelib/fs.stat" "^2.0.1" + "@nodelib/fs.walk" "^1.2.1" + glob-parent "^5.0.0" + is-glob "^4.0.1" + merge2 "^1.2.3" + micromatch "^4.0.2" + +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +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" + +fsevents@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" + integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== + +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" + integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +is-binary-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + +lodash@^4.17.5: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +merge2@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.4.tgz#c9269589e6885a60cf80605d9522d4b67ca646e3" + integrity sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A== + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +ms@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +picomatch@^2.0.4, picomatch@^2.0.5: + version "2.0.7" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" + integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== + +readdirp@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" + integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== + dependencies: + picomatch "^2.0.4" + +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +webpack-merge@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4" + integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw== + dependencies: + lodash "^4.17.5" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0"