summaryrefslogtreecommitdiff
path: root/electron/service
diff options
context:
space:
mode:
Diffstat (limited to 'electron/service')
-rw-r--r--electron/service/commands/build-main.js41
-rw-r--r--electron/service/commands/build.js44
-rw-r--r--electron/service/commands/dev.js102
-rw-r--r--electron/service/config/base.js126
-rw-r--r--electron/service/config/css.js72
-rw-r--r--electron/service/config/dev.js43
-rw-r--r--electron/service/config/main.js73
-rw-r--r--electron/service/config/prod.js41
-rw-r--r--electron/service/config/renderer.js45
-rw-r--r--electron/service/config/terserOptions.js42
-rw-r--r--electron/service/project.config.js16
-rw-r--r--electron/service/utils/getLocalIP.js17
-rw-r--r--electron/service/utils/loadEnv.js39
-rw-r--r--electron/service/utils/logger.js72
-rw-r--r--electron/service/utils/paths.js6
-rw-r--r--electron/service/utils/resolveClientEnv.js11
-rw-r--r--electron/service/utils/spinner.js57
17 files changed, 847 insertions, 0 deletions
diff --git a/electron/service/commands/build-main.js b/electron/service/commands/build-main.js
new file mode 100644
index 00000000..1b28ef91
--- /dev/null
+++ b/electron/service/commands/build-main.js
@@ -0,0 +1,41 @@
+'use strict'
+
+const loadEnv = require('../utils/loadEnv')
+loadEnv()
+loadEnv('production')
+
+const rm = require('rimraf')
+const webpack = require('webpack')
+
+const { error, done } = require('../utils/logger')
+const { logWithSpinner, stopSpinner } = require('../utils/spinner')
+const paths = require('../utils/paths')
+// after renderer is built, main is next to build
+const webpackConfig = require('../config/main')
+const config = require('../project.config')
+
+logWithSpinner('Building for production...')
+// removed rm function to prevent the deletion of renderer
+webpack(webpackConfig, (err, stats) => {
+ stopSpinner(false)
+
+ if (err) throw err
+
+ process.stdout.write(
+ stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false,
+ }) + '\n\n'
+ )
+
+ if (stats.hasErrors()) {
+ error('Build failed with errors.\n')
+ process.exit(1)
+ }
+
+ done('Build complete.\n')
+})
+
diff --git a/electron/service/commands/build.js b/electron/service/commands/build.js
new file mode 100644
index 00000000..097f7013
--- /dev/null
+++ b/electron/service/commands/build.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const loadEnv = require('../utils/loadEnv')
+loadEnv()
+loadEnv('production')
+
+const rm = require('rimraf')
+const webpack = require('webpack')
+
+const { error, done } = require('../utils/logger')
+const { logWithSpinner, stopSpinner } = require('../utils/spinner')
+const paths = require('../utils/paths')
+// build renderer first
+const webpackConfig = require('../config/renderer')
+const config = require('../project.config')
+
+logWithSpinner('Building for production...')
+
+rm(paths.resolve(config.outputDir), (err) => {
+ if (err) throw err
+
+ webpack(webpackConfig, (err, stats) => {
+ stopSpinner(false)
+
+ if (err) throw err
+
+ process.stdout.write(
+ stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false,
+ }) + '\n\n'
+ )
+
+ if (stats.hasErrors()) {
+ error('Build failed with errors.\n')
+ process.exit(1)
+ }
+
+ done('Build complete.\n')
+ })
+})
diff --git a/electron/service/commands/dev.js b/electron/service/commands/dev.js
new file mode 100644
index 00000000..bea42021
--- /dev/null
+++ b/electron/service/commands/dev.js
@@ -0,0 +1,102 @@
+'use strict'
+
+const loadEnv = require('../utils/loadEnv')
+loadEnv()
+loadEnv('development')
+const chalk = require('chalk')
+const webpack = require('webpack')
+const WebpackDevServer = require('webpack-dev-server')
+const { info } = require('../utils/logger')
+const getLocalIP = require('../utils/getLocalIP')
+const devWebpackConfig = require('../config/dev')
+const devServerOptions = devWebpackConfig.devServer
+const { spawn } = require('node:child_process')
+const electron = require('electron')
+const path = require('path')
+const url = require('url')
+const fs = require('fs');
+
+let electronProcess = null
+let manualRestart = false
+// disable warnings in browser console
+process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'false'
+
+const protocol = devServerOptions.https ? 'https' : 'http'
+const host = devServerOptions.host || '0.0.0.0'
+const port = devServerOptions.port || 8080
+
+// older code that sets the url for the path I would assume
+var parseArg = process.argv[2] || ""
+var yarnArg = url.parse(parseArg)
+
+function resetPort() {
+ let resetData = { "_comment1": "port should not be declared when commiting" }
+ fs.writeFileSync(path.join(__dirname, "../../src/lib/flaskserverport.json"), JSON.stringify(resetData), 'utf8')
+ console.log("Resetting the flaskport")
+}
+
+function startElectron(webpackport) {
+var wbport = webpackport
+ // this sends url to proper position
+ process.argv.shift()
+ process.argv.shift()
+ // get URL from PrintPDF
+ // checks if url is http
+ if (yarnArg.protocol) {
+ var args = [
+ '--inspect=5858',
+ path.join(__dirname, '../../dist/electron/main.js')
+ ].concat(process.argv)
+ } else {
+ var args = [
+ '--inspect=5858',
+ `http://0.0.0.0:${wbport}/#${process.argv}`
+ ].concat(process.argv)
+ }
+ // detect yarn or npm and process commandline args accordingly
+ if (process.env.npm_execpath.endsWith('yarn.js')) {
+ args = args.concat(process.argv.slice(3))
+ } else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
+ args = args.concat(process.argv.slice(2))
+ }
+ electronProcess = spawn(electron, args)
+ electronProcess.on('close', () => {
+ if (!manualRestart) {
+ process.exit()
+ } else {
+ process.kill(electronProcess.pid)
+ }
+ resetPort()
+ })
+ electronProcess.on('exit', () => {
+ resetPort()
+ })
+}
+
+info('Starting development server...')
+const compiler = webpack(devWebpackConfig)
+const server = new WebpackDevServer(devServerOptions, compiler)
+
+compiler.hooks.done.tap('serve', (stats) => {
+ console.log()
+ console.log()
+ console.log(`App running at:`)
+ console.log(` - Local: ${chalk.cyan(`${protocol}://${host}:${port}`)}`)
+ console.log(` - Network: ${chalk.cyan(`${protocol}://${getLocalIP()}:${port}`)}`)
+ console.log()
+
+ // allows livereload for webpack devserver to work without multiple instances of electron
+ if (electronProcess) {
+ manualRestart = true
+ } else {
+ manualRestart = false
+ // starts nodejs electron commandline browser
+ startElectron(devServerOptions.port)
+ }
+})
+
+server.start(port, host, (err) => {
+ if (err) {
+ process.exit(0)
+ }
+})
diff --git a/electron/service/config/base.js b/electron/service/config/base.js
new file mode 100644
index 00000000..8743f364
--- /dev/null
+++ b/electron/service/config/base.js
@@ -0,0 +1,126 @@
+'use strict'
+
+const { DefinePlugin, EnvironmentPlugin } = require('webpack')
+const { VueLoaderPlugin } = require('vue-loader')
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
+const HTMLPlugin = require('html-webpack-plugin')
+const { VuetifyPlugin } = require('webpack-plugin-vuetify')
+
+const resolveClientEnv = require('../utils/resolveClientEnv')
+const paths = require('../utils/paths')
+
+const config = require('../project.config')
+
+const isProd = process.env.NODE_ENV === 'production'
+
+module.exports = {
+ context: process.cwd(),
+
+ output: {
+ path: paths.resolve(config.outputDir),
+ publicPath: config.dev.publicPath,
+ filename: '[name].js',
+ },
+
+ resolve: {
+ alias: {
+ '@': paths.resolve('src'),
+ },
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.json', 'html', 'ejs'],
+ },
+
+ plugins: [
+ new VueLoaderPlugin(),
+ new EnvironmentPlugin(['NODE_ENV']),
+ new CaseSensitivePathsPlugin(),
+ new HTMLPlugin({
+ template: paths.resolve('src/index.html'),
+ templateParameters: {
+ ...resolveClientEnv(
+ { publicPath: isProd ? config.build.publicPath : config.dev.publicPath },
+ false /* raw */
+ ),
+ },
+ }),
+ new VuetifyPlugin({ autoImport: true }),
+ new DefinePlugin({
+ // vue3 feature flags <http://link.vuejs.org/feature-flags>
+ __VUE_OPTIONS_API__: 'true',
+ __VUE_PROD_DEVTOOLS__: 'false',
+
+ ...resolveClientEnv({
+ publicPath: isProd ? config.build.publicPath : config.dev.publicPath,
+ }),
+ }),
+ ],
+
+ module: {
+ noParse: /^(vue|vue-router)$/,
+
+ rules: [
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
+ },
+ // babel
+ {
+ test: /\.m?jsx?$/,
+ exclude: (file) => {
+ // always transpile js in vue files
+ if (/\.vue\.jsx?$/.test(file)) {
+ return false
+ }
+ // Don't transpile node_modules
+ return /node_modules/.test(file)
+ },
+ use: ['thread-loader', 'babel-loader'],
+ },
+
+ // ts
+ {
+ test: /\.tsx?$/,
+ use: [
+ 'thread-loader',
+ 'babel-loader',
+ {
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true,
+ appendTsSuffixTo: ['\\.vue$'],
+ happyPackMode: true,
+ },
+ },
+ ],
+ },
+
+ // images
+ {
+ test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
+ type: 'asset',
+ generator: { filename: 'img/[contenthash:8][ext][query]' },
+ },
+
+ // do not base64-inline SVGs.
+ // https://github.com/facebookincubator/create-react-app/pull/1180
+ {
+ test: /\.(svg)(\?.*)?$/,
+ type: 'asset/resource',
+ generator: { filename: 'img/[contenthash:8][ext][query]' },
+ },
+
+ // media
+ {
+ test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+ type: 'asset',
+ generator: { filename: 'media/[contenthash:8][ext][query]' },
+ },
+
+ // fonts
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
+ type: 'asset',
+ generator: { filename: 'fonts/[contenthash:8][ext][query]' },
+ },
+ ],
+ },
+}
diff --git a/electron/service/config/css.js b/electron/service/config/css.js
new file mode 100644
index 00000000..3fb5893e
--- /dev/null
+++ b/electron/service/config/css.js
@@ -0,0 +1,72 @@
+'use strict'
+
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+
+const isProd = process.env.NODE_ENV === 'production'
+
+const plugins = []
+if (isProd) {
+ const filename = 'css/[name].[contenthash:8].css'
+
+ plugins.push(
+ new MiniCssExtractPlugin({
+ filename,
+ chunkFilename: filename,
+ })
+ )
+}
+
+const genStyleRules = () => {
+ const cssLoader = {
+ loader: 'css-loader',
+ options: {
+ // how many loaders before css-loader should be applied to [@import]ed resources.
+ // stylePostLoader injected by vue-loader + postcss-loader
+ importLoaders: 1 + 1,
+ esModule: false, // css-loader using ES Modules as default in v4, but vue-style-loader support cjs only.
+ },
+ }
+ const postcssLoader = {
+ loader: 'postcss-loader',
+ options: {
+ postcssOptions: {
+ plugins: [require('autoprefixer')]
+ },
+ },
+ }
+ const extractPluginLoader = {
+ loader: MiniCssExtractPlugin.loader,
+ }
+ const vueStyleLoader = {
+ loader: 'vue-style-loader',
+ }
+
+ function createCSSRule(test, loader, loaderOptions) {
+ const loaders = [cssLoader, postcssLoader]
+
+ if (isProd) {
+ loaders.unshift(extractPluginLoader)
+ } else {
+ loaders.unshift(vueStyleLoader)
+ }
+
+ if (loader) {
+ loaders.push({ loader, options: loaderOptions })
+ }
+
+ return { test, use: loaders }
+ }
+
+ return [
+ createCSSRule(/\.css$/),
+ createCSSRule(/\.p(ost)?css$/),
+ createCSSRule(/\.scss$/, 'sass-loader')
+ ]
+}
+
+module.exports = {
+ plugins,
+ module: {
+ rules: genStyleRules(),
+ },
+}
diff --git a/electron/service/config/dev.js b/electron/service/config/dev.js
new file mode 100644
index 00000000..42a82b37
--- /dev/null
+++ b/electron/service/config/dev.js
@@ -0,0 +1,43 @@
+'use strict'
+
+const { merge } = require('webpack-merge')
+
+const baseWebpackConfig = require('./base')
+const cssWebpackConfig = require('./css')
+const config = require('../project.config')
+const { ProvidePlugin, DefinePlugin } = require('webpack')
+
+
+module.exports = merge(baseWebpackConfig, cssWebpackConfig, {
+ entry: {
+ main: './src/renderer/main.js'
+ },
+
+ mode: 'development',
+
+ devtool: 'eval-cheap-module-source-map',
+
+ devServer: {
+ watchFiles: ['src/**/*'],
+ historyApiFallback: {
+ rewrites: [{ from: /./, to: '/index.html' }],
+ },
+ devMiddleware: {
+ publicPath: config.dev.publicPath,
+ },
+ open: false,
+ host: '0.0.0.0',
+ port: 'auto',
+ liveReload: true,
+ },
+
+ infrastructureLogging: {
+ level: 'warn',
+ },
+
+ stats: {
+ assets: false,
+ modules: false,
+ errorDetails: false,
+ },
+})
diff --git a/electron/service/config/main.js b/electron/service/config/main.js
new file mode 100644
index 00000000..3083dea0
--- /dev/null
+++ b/electron/service/config/main.js
@@ -0,0 +1,73 @@
+'use strict'
+
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
+const config = require('../project.config')
+
+const resolveClientEnv = require('../utils/resolveClientEnv')
+const paths = require('../utils/paths')
+const { merge } = require('webpack-merge')
+const TerserPlugin = require('terser-webpack-plugin')
+const cssWebpackConfig = require('./css')
+const terserOptions = require('./terserOptions')
+const isProd = process.env.NODE_ENV === 'production'
+
+module.exports = merge(cssWebpackConfig, {
+ context: process.cwd(),
+ mode: 'production',
+ entry: {
+ main: './src/main/index.js',
+ preload: './src/main/preload.js',
+ },
+
+ node: {
+ __dirname: false,
+ },
+
+ optimization: {
+ minimize: true,
+ minimizer: [new TerserPlugin(terserOptions())],
+ moduleIds: 'named',
+ },
+ target: ['electron-main'],
+
+ output: {
+ path: paths.resolve(config.outputDir),
+ publicPath: config.dev.publicPath,
+ filename: '[name].js',
+ },
+
+ resolve: {
+ alias: {
+ '@': paths.resolve('src'),
+ },
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.json', 'html', 'ejs'],
+ },
+
+ plugins: [
+ new CaseSensitivePathsPlugin(),
+ ],
+
+ module: {
+ noParse: /^(vue|vue-router)$/,
+
+ rules: [
+ // ts
+ {
+ test: /\.tsx?$/,
+ use: [
+ 'thread-loader',
+ 'babel-loader',
+ {
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true,
+ appendTsSuffixTo: ['\\.vue$'],
+ happyPackMode: true,
+ },
+ },
+ ],
+ },
+ ],
+ },
+}
+) \ No newline at end of file
diff --git a/electron/service/config/prod.js b/electron/service/config/prod.js
new file mode 100644
index 00000000..1d9e8726
--- /dev/null
+++ b/electron/service/config/prod.js
@@ -0,0 +1,41 @@
+'use strict'
+
+const { merge } = require('webpack-merge')
+const TerserPlugin = require('terser-webpack-plugin')
+
+const baseWebpackConfig = require('./base')
+const cssWebpackConfig = require('./css')
+const config = require('../project.config')
+const terserOptions = require('./terserOptions')
+
+module.exports = merge(baseWebpackConfig, cssWebpackConfig, {
+ mode: 'production',
+
+ output: {
+ publicPath: config.build.publicPath,
+ },
+
+ optimization: {
+ minimize: true,
+ minimizer: [new TerserPlugin(terserOptions())],
+ moduleIds: 'deterministic',
+ moduleIds: 'named',
+ splitChunks: {
+ cacheGroups: {
+ defaultVendors: {
+ name: `chunk-vendors`,
+ test: /[\\/]node_modules[\\/]/,
+ priority: -10,
+ chunks: 'initial',
+ },
+ common: {
+ name: `chunk-common`,
+ minChunks: 2,
+ priority: -20,
+ chunks: 'initial',
+ reuseExistingChunk: true,
+ },
+ },
+ },
+ },
+})
diff --git a/electron/service/config/renderer.js b/electron/service/config/renderer.js
new file mode 100644
index 00000000..cf3fab01
--- /dev/null
+++ b/electron/service/config/renderer.js
@@ -0,0 +1,45 @@
+'use strict'
+
+const { merge } = require('webpack-merge')
+const TerserPlugin = require('terser-webpack-plugin')
+
+const baseWebpackConfig = require('./base')
+const cssWebpackConfig = require('./css')
+const config = require('../project.config')
+const terserOptions = require('./terserOptions')
+
+module.exports = merge(baseWebpackConfig, cssWebpackConfig, {
+ mode: 'production',
+ entry: {
+ renderer: './src/renderer/main.js',
+ },
+
+ output: {
+ publicPath: config.build.publicPath,
+ },
+
+ optimization: {
+ minimize: true,
+ minimizer: [new TerserPlugin(terserOptions())],
+ moduleIds: 'deterministic',
+ moduleIds: 'named',
+ splitChunks: {
+ cacheGroups: {
+ defaultVendors: {
+ name: `chunk-vendors`,
+ test: /[\\/]node_modules[\\/]/,
+ priority: -10,
+ chunks: 'initial',
+ },
+ common: {
+ name: `chunk-common`,
+ minChunks: 2,
+ priority: -20,
+ chunks: 'initial',
+ reuseExistingChunk: true,
+ },
+ },
+ },
+ },
+ target: ['electron-renderer'],
+})
diff --git a/electron/service/config/terserOptions.js b/electron/service/config/terserOptions.js
new file mode 100644
index 00000000..134a3258
--- /dev/null
+++ b/electron/service/config/terserOptions.js
@@ -0,0 +1,42 @@
+'use strict'
+
+module.exports = (options) => ({
+ terserOptions: {
+ compress: {
+ // turn off flags with small gains to speed up minification
+ arrows: false,
+ collapse_vars: false, // 0.3kb
+ comparisons: false,
+ computed_props: false,
+ hoist_funs: false,
+ hoist_props: false,
+ hoist_vars: false,
+ inline: false,
+ loops: false,
+ negate_iife: false,
+ properties: false,
+ reduce_funcs: false,
+ reduce_vars: false,
+ switches: false,
+ toplevel: false,
+ typeofs: false,
+
+ // a few flags with noticable gains/speed ratio
+ // numbers based on out of the box vendor bundle
+ booleans: true, // 0.7kb
+ if_return: true, // 0.4kb
+ sequences: true, // 0.7kb
+ unused: true, // 2.3kb
+
+ // required features to drop conditional branches
+ conditionals: true,
+ dead_code: true,
+ evaluate: true,
+ },
+ mangle: {
+ safari10: true,
+ },
+ },
+ // parallel: options.parallel,
+ extractComments: false,
+})
diff --git a/electron/service/project.config.js b/electron/service/project.config.js
new file mode 100644
index 00000000..7e7df9c1
--- /dev/null
+++ b/electron/service/project.config.js
@@ -0,0 +1,16 @@
+'use strict'
+
+module.exports = {
+ // orginal was dist
+ outputDir: 'dist/electron',
+
+ dev: {
+ publicPath: '/',
+ port: 8080,
+ },
+
+ build: {
+ // orginal was /
+ publicPath: './',
+ },
+}
diff --git a/electron/service/utils/getLocalIP.js b/electron/service/utils/getLocalIP.js
new file mode 100644
index 00000000..14926ee2
--- /dev/null
+++ b/electron/service/utils/getLocalIP.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const os = require('os')
+
+module.exports = function getLocalIP() {
+ const interfaces = os.networkInterfaces()
+
+ for (const devName in interfaces) {
+ const iface = interfaces[devName]
+ for (let i = 0; i < iface.length; i++) {
+ const alias = iface[i]
+ if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
+ return alias.address
+ }
+ }
+ }
+}
diff --git a/electron/service/utils/loadEnv.js b/electron/service/utils/loadEnv.js
new file mode 100644
index 00000000..3c054ff6
--- /dev/null
+++ b/electron/service/utils/loadEnv.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const path = require('path')
+const dotenv = require('dotenv')
+const dotenvExpand = require('dotenv-expand')
+const { error } = require('./logger')
+
+module.exports = function loadEnv(mode) {
+ const basePath = path.resolve(process.cwd(), `.env${mode ? `.${mode}` : ``}`)
+ const localPath = `${basePath}.local`
+
+ const load = (envPath) => {
+ try {
+ const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
+ dotenvExpand.expand(env)
+ } catch (err) {
+ // only ignore error if file is not found
+ if (err.toString().indexOf('ENOENT') < 0) {
+ error(err)
+ }
+ }
+ }
+
+ load(localPath)
+ load(basePath)
+
+ // by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
+ // is production or test. However the value in .env files will take higher
+ // priority.
+ if (mode) {
+ const defaultNodeEnv = mode === 'production' || mode === 'test' ? mode : 'development'
+ if (process.env.NODE_ENV == null) {
+ process.env.NODE_ENV = defaultNodeEnv
+ }
+ if (process.env.BABEL_ENV == null) {
+ process.env.BABEL_ENV = defaultNodeEnv
+ }
+ }
+}
diff --git a/electron/service/utils/logger.js b/electron/service/utils/logger.js
new file mode 100644
index 00000000..0d74c52c
--- /dev/null
+++ b/electron/service/utils/logger.js
@@ -0,0 +1,72 @@
+'use strict'
+
+const chalk = require('chalk')
+const stripAnsi = require('strip-ansi')
+const readline = require('readline')
+const EventEmitter = require('events')
+
+exports.events = new EventEmitter()
+
+function _log(type, tag, message) {
+ if (process.env.VUE_CLI_API_MODE && message) {
+ exports.events.emit('log', {
+ message,
+ type,
+ tag,
+ })
+ }
+}
+
+const format = (label, msg) => {
+ return msg
+ .split('\n')
+ .map((line, i) => {
+ return i === 0 ? `${label} ${line}` : line.padStart(stripAnsi(label).length)
+ })
+ .join('\n')
+}
+
+const chalkTag = (msg) => chalk.bgBlackBright.white.dim(` ${msg} `)
+
+exports.log = (msg = '', tag = null) => {
+ tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
+ _log('log', tag, msg)
+}
+
+exports.info = (msg, tag = null) => {
+ console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg))
+ _log('info', tag, msg)
+}
+
+exports.done = (msg, tag = null) => {
+ console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg))
+ _log('done', tag, msg)
+}
+
+exports.warn = (msg, tag = null) => {
+ console.warn(
+ format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg))
+ )
+ _log('warn', tag, msg)
+}
+
+exports.error = (msg, tag = null) => {
+ console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)))
+ _log('error', tag, msg)
+ if (msg instanceof Error) {
+ console.error(msg.stack)
+ _log('error', tag, msg.stack)
+ }
+}
+
+exports.clearConsole = (title) => {
+ if (process.stdout.isTTY) {
+ const blank = '\n'.repeat(process.stdout.rows)
+ console.log(blank)
+ readline.cursorTo(process.stdout, 0, 0)
+ readline.clearScreenDown(process.stdout)
+ if (title) {
+ console.log(title)
+ }
+ }
+}
diff --git a/electron/service/utils/paths.js b/electron/service/utils/paths.js
new file mode 100644
index 00000000..a3e173c3
--- /dev/null
+++ b/electron/service/utils/paths.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const path = require('path')
+
+// gen absolute path
+exports.resolve = (...args) => path.posix.join(process.cwd(), ...args)
diff --git a/electron/service/utils/resolveClientEnv.js b/electron/service/utils/resolveClientEnv.js
new file mode 100644
index 00000000..4a4505a6
--- /dev/null
+++ b/electron/service/utils/resolveClientEnv.js
@@ -0,0 +1,11 @@
+'use strict'
+const prefixRE = /^VUE_APP_/
+
+module.exports = function resolveClientEnv(options, raw) {
+ process.env.PUBLIC_PATH = options.publicPath
+
+ if (raw) {
+ return env
+ }
+
+} \ No newline at end of file
diff --git a/electron/service/utils/spinner.js b/electron/service/utils/spinner.js
new file mode 100644
index 00000000..d643a933
--- /dev/null
+++ b/electron/service/utils/spinner.js
@@ -0,0 +1,57 @@
+'use strict'
+
+const ora = require('ora')
+const chalk = require('chalk')
+
+const spinner = ora()
+let lastMsg = null
+let isPaused = false
+
+exports.logWithSpinner = (symbol, msg) => {
+ if (!msg) {
+ msg = symbol
+ symbol = chalk.green('✔')
+ }
+ if (lastMsg) {
+ spinner.stopAndPersist({
+ symbol: lastMsg.symbol,
+ text: lastMsg.text,
+ })
+ }
+ spinner.text = ' ' + msg
+ lastMsg = {
+ symbol: symbol + ' ',
+ text: msg,
+ }
+ spinner.start()
+}
+
+exports.stopSpinner = (persist) => {
+ if (lastMsg && persist !== false) {
+ spinner.stopAndPersist({
+ symbol: lastMsg.symbol,
+ text: lastMsg.text,
+ })
+ } else {
+ spinner.stop()
+ }
+ lastMsg = null
+}
+
+exports.pauseSpinner = () => {
+ if (spinner.isSpinning) {
+ spinner.stop()
+ isPaused = true
+ }
+}
+
+exports.resumeSpinner = () => {
+ if (isPaused) {
+ spinner.start()
+ isPaused = false
+ }
+}
+
+exports.failSpinner = (text) => {
+ spinner.fail(text)
+}