feat: try no gui mode
parent
3ae6282870
commit
164532f3b7
|
@ -42,14 +42,17 @@ function newServerStart ({ mitmproxyPath }) {
|
||||||
}
|
}
|
||||||
server.start = newServerStart
|
server.start = newServerStart
|
||||||
async function startup ({ mitmproxyPath }) {
|
async function startup ({ mitmproxyPath }) {
|
||||||
|
let server
|
||||||
const conf = config.get()
|
const conf = config.get()
|
||||||
|
|
||||||
if (conf.server.enabled) {
|
if (conf.server.enabled) {
|
||||||
try {
|
try {
|
||||||
await server.start({ mitmproxyPath })
|
server = await server.start({ mitmproxyPath })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('代理服务启动失败:', err)
|
log.error('代理服务启动失败:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conf.proxy.enabled) {
|
if (conf.proxy.enabled) {
|
||||||
try {
|
try {
|
||||||
await proxy.start()
|
await proxy.start()
|
||||||
|
@ -57,8 +60,10 @@ async function startup ({ mitmproxyPath }) {
|
||||||
log.error('开启系统代理失败:', err)
|
log.error('开启系统代理失败:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const plugins = []
|
const plugins = []
|
||||||
|
|
||||||
for (const key in plugin) {
|
for (const key in plugin) {
|
||||||
if (conf.plugin[key].enabled) {
|
if (conf.plugin[key].enabled) {
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
|
@ -72,12 +77,15 @@ async function startup ({ mitmproxyPath }) {
|
||||||
plugins.push(start())
|
plugins.push(start())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugins && plugins.length > 0) {
|
if (plugins && plugins.length > 0) {
|
||||||
await Promise.all(plugins)
|
await Promise.all(plugins)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('开启插件失败:', err)
|
log.error('开启插件失败:', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shutdown () {
|
async function shutdown () {
|
||||||
|
|
|
@ -30,7 +30,7 @@ const serverApi = {
|
||||||
return this.close()
|
return this.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async start ({ mitmproxyPath, plugins }) {
|
async start ({ mitmproxyPath, plugins, options }) {
|
||||||
const allConfig = config.get()
|
const allConfig = config.get()
|
||||||
const serverConfig = lodash.cloneDeep(allConfig.server)
|
const serverConfig = lodash.cloneDeep(allConfig.server)
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ const serverApi = {
|
||||||
const runningConfigPath = path.join(basePath, '/running.json')
|
const runningConfigPath = path.join(basePath, '/running.json')
|
||||||
fs.writeFileSync(runningConfigPath, jsonApi.stringify(serverConfig))
|
fs.writeFileSync(runningConfigPath, jsonApi.stringify(serverConfig))
|
||||||
log.info('保存 running.json 运行时配置文件成功:', runningConfigPath)
|
log.info('保存 running.json 运行时配置文件成功:', runningConfigPath)
|
||||||
const serverProcess = fork(mitmproxyPath, [runningConfigPath])
|
const serverProcess = fork(mitmproxyPath, [runningConfigPath], options)
|
||||||
server = {
|
server = {
|
||||||
id: serverProcess.pid,
|
id: serverProcess.pid,
|
||||||
process: serverProcess,
|
process: serverProcess,
|
||||||
|
@ -86,6 +86,7 @@ const serverApi = {
|
||||||
serverProcess.send({ type: 'action', event: { key: 'close' } })
|
serverProcess.send({ type: 'action', event: { key: 'close' } })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
serverProcess.on('beforeExit', (code) => {
|
serverProcess.on('beforeExit', (code) => {
|
||||||
log.warn('server process beforeExit, code:', code)
|
log.warn('server process beforeExit, code:', code)
|
||||||
})
|
})
|
||||||
|
@ -113,7 +114,8 @@ const serverApi = {
|
||||||
event.fire('speed', msg.event)
|
event.fire('speed', msg.event)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return { port: serverConfig.port }
|
|
||||||
|
return { port: serverConfig.port, server }
|
||||||
},
|
},
|
||||||
async kill () {
|
async kill () {
|
||||||
if (server) {
|
if (server) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ function log4jsConfigure (categories) {
|
||||||
|
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
config.appenders[category] = { ...appenderConfig, filename: path.join(basePath, `/${category}.log`) }
|
config.appenders[category] = { ...appenderConfig, filename: path.join(basePath, `/${category}.log`) }
|
||||||
config.categories[category] = { appenders: [category, 'std'], level }
|
config.categories[category] = { appenders: [category, ...process.env.NO_CONSOLE_LOG ? [] : ['std']], level }
|
||||||
}
|
}
|
||||||
|
|
||||||
log4js.configure(config)
|
log4js.configure(config)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
____ _____ _ __
|
||||||
|
/ __ \___ _ __ / ___/(_)___/ /__ _________ ______
|
||||||
|
/ / / / _ \ | / /_____\__ \/ / __ / _ \/ ___/ __ `/ ___/
|
||||||
|
/ /_/ / __/ |/ /_____/__/ / / /_/ / __/ /__/ /_/ / /
|
||||||
|
/_____/\___/|___/ /____/_/\__,_/\___/\___/\__,_/_/
|
||||||
|
|
||||||
|
|
||||||
|
==================== 开发者边车 ====================
|
|
@ -13,6 +13,7 @@
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"electron:build": "vue-cli-service electron:build",
|
"electron:build": "vue-cli-service electron:build",
|
||||||
"electron": "vue-cli-service electron:serve",
|
"electron": "vue-cli-service electron:serve",
|
||||||
|
"electron:headless": "cross-env ELECTRON_RUN_AS_NODE=1 vue-cli-service electron:serve",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"postuninstall": "electron-builder install-app-deps",
|
"postuninstall": "electron-builder install-app-deps",
|
||||||
"electron:icons": "electron-icon-builder --input=./public/logo/win.png --output=build --flatten",
|
"electron:icons": "electron-icon-builder --input=./public/logo/win.png --output=build --flatten",
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
"@vscode/sudo-prompt": "^9.3.1",
|
"@vscode/sudo-prompt": "^9.3.1",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"ant-design-vue": "^1.7.8",
|
"ant-design-vue": "^1.7.8",
|
||||||
|
"cac": "^6.7.14",
|
||||||
"electron-baidu-tongji": "^1.0.5",
|
"electron-baidu-tongji": "^1.0.5",
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
|
@ -44,6 +46,7 @@
|
||||||
"@vue/babel-preset-jsx": "^1.4.0",
|
"@vue/babel-preset-jsx": "^1.4.0",
|
||||||
"@vue/cli-plugin-babel": "^5.0.8",
|
"@vue/cli-plugin-babel": "^5.0.8",
|
||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"electron": "^19.1.9",
|
"electron": "^19.1.9",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
"electron-icon-builder": "^2.0.1",
|
"electron-icon-builder": "^2.0.1",
|
||||||
|
|
|
@ -1,490 +1,38 @@
|
||||||
'use strict'
|
|
||||||
/* global __static */
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import DevSidecar from '@docmirror/dev-sidecar'
|
import { fork } from 'node:child_process'
|
||||||
import { app, BrowserWindow, dialog, globalShortcut, ipcMain, Menu, nativeImage, nativeTheme, powerMonitor, protocol, Tray } from 'electron'
|
|
||||||
import minimist from 'minimist'
|
|
||||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
|
||||||
import backend from './bridge/backend'
|
|
||||||
import jsonApi from '@docmirror/mitmproxy/src/json'
|
|
||||||
import log from './utils/util.log'
|
|
||||||
|
|
||||||
const isWindows = process.platform === 'win32'
|
|
||||||
const isMac = process.platform === 'darwin'
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||||
|
const RUN_AS_NODE = !!process.env.ELECTRON_RUN_AS_NODE
|
||||||
|
|
||||||
// 避免其他系统出现异常,只有 Windows 使用 './background/powerMonitor'
|
;(async () => {
|
||||||
let _powerMonitor = powerMonitor
|
if (RUN_AS_NODE) {
|
||||||
if (isWindows) {
|
await startHeadless()
|
||||||
try {
|
|
||||||
_powerMonitor = require('./background/powerMonitor').powerMonitor
|
|
||||||
} catch (e) {
|
|
||||||
log.error(`加载 './background/powerMonitor' 失败,现捕获异常并使用默认的 powerMonitor。\r\n目前,启动着DS重启电脑时,将无法正常关闭系统代理,届时请自行关闭系统代理!\r\n捕获的异常信息:`, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
|
||||||
// be closed automatically when the JavaScript object is garbage collected.
|
|
||||||
let win
|
|
||||||
let winIsHidden = false
|
|
||||||
|
|
||||||
let tray // 防止被内存清理
|
|
||||||
let forceClose = false
|
|
||||||
DevSidecar.api.config.reload()
|
|
||||||
let hideDockWhenWinClose = DevSidecar.api.config.get().app.dock.hideWhenWinClose || false
|
|
||||||
// Scheme must be registered before the app is ready
|
|
||||||
protocol.registerSchemesAsPrivileged([
|
|
||||||
{ scheme: 'app', privileges: { secure: true, standard: true } },
|
|
||||||
])
|
|
||||||
|
|
||||||
function openDevTools () {
|
|
||||||
try {
|
|
||||||
log.debug('尝试打开 `开发者工具`')
|
|
||||||
win.webContents.openDevTools()
|
|
||||||
log.debug('打开 `开发者工具` 成功')
|
|
||||||
} catch (e) {
|
|
||||||
log.error('打开 `开发者工具` 失败:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDevTools () {
|
|
||||||
try {
|
|
||||||
log.debug('尝试关闭 `开发者工具`')
|
|
||||||
win.webContents.closeDevTools()
|
|
||||||
log.debug('关闭 `开发者工具` 成功')
|
|
||||||
} catch (e) {
|
|
||||||
log.error('关闭 `开发者工具` 失败:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchDevTools () {
|
|
||||||
if (!win || !win.webContents) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (win.webContents.isDevToolsOpened()) {
|
|
||||||
closeDevTools()
|
|
||||||
} else {
|
} else {
|
||||||
openDevTools()
|
await startGUI()
|
||||||
}
|
}
|
||||||
}
|
})()
|
||||||
|
|
||||||
// 隐藏主窗口,并创建托盘,绑定关闭事件
|
async function startHeadless () {
|
||||||
function setTray () {
|
const cli = path.join(__dirname, 'dev-sidecar-cli.js')
|
||||||
// const topMenu = Menu.buildFromTemplate({})
|
let argv
|
||||||
// Menu.setApplicationMenu(topMenu)
|
if (isDevelopment) {
|
||||||
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
|
argv = process.argv.splice(3)
|
||||||
// 通常被添加到一个 context menu 上.
|
|
||||||
// 系统托盘右键菜单
|
|
||||||
const trayMenuTemplate = [
|
|
||||||
{
|
|
||||||
// 系统托盘图标目录
|
|
||||||
label: 'DevTools (F12)',
|
|
||||||
click: switchDevTools,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// 系统托盘图标目录
|
|
||||||
label: '退出',
|
|
||||||
click: () => {
|
|
||||||
log.info('force quit')
|
|
||||||
forceClose = true
|
|
||||||
quit()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
// 设置系统托盘图标
|
|
||||||
const iconRootPath = path.join(__dirname, '../extra/icons/tray')
|
|
||||||
let iconPath = path.join(iconRootPath, 'icon.png')
|
|
||||||
const iconWhitePath = path.join(iconRootPath, 'icon-white.png')
|
|
||||||
const iconBlackPath = path.join(iconRootPath, 'icon-black.png')
|
|
||||||
if (isMac) {
|
|
||||||
iconPath = nativeTheme.shouldUseDarkColors ? iconWhitePath : iconBlackPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const trayIcon = nativeImage.createFromPath(iconPath)
|
|
||||||
const appTray = new Tray(trayIcon)
|
|
||||||
|
|
||||||
// 当桌面主题更新时
|
|
||||||
if (isMac) {
|
|
||||||
nativeTheme.on('updated', () => {
|
|
||||||
console.log('i am changed')
|
|
||||||
if (nativeTheme.shouldUseDarkColors) {
|
|
||||||
console.log('i am dark.')
|
|
||||||
tray.setImage(iconWhitePath)
|
|
||||||
} else {
|
|
||||||
console.log('i am light.')
|
|
||||||
tray.setImage(iconBlackPath)
|
|
||||||
// tray.setPressedImage(iconWhitePath)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图标的上下文菜单
|
|
||||||
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
|
|
||||||
|
|
||||||
// 设置托盘悬浮提示
|
|
||||||
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
|
||||||
// 单击托盘小图标显示应用
|
|
||||||
appTray.on('click', () => {
|
|
||||||
// 显示主程序
|
|
||||||
showWin()
|
|
||||||
})
|
|
||||||
|
|
||||||
appTray.on('right-click', () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
appTray.popUpContextMenu(contextMenu)
|
|
||||||
}, 200)
|
|
||||||
})
|
|
||||||
|
|
||||||
return appTray
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLinux () {
|
|
||||||
const platform = DevSidecar.api.shell.getSystemPlatform()
|
|
||||||
return platform === 'linux'
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideWin () {
|
|
||||||
if (win) {
|
|
||||||
if (isLinux()) {
|
|
||||||
quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
win.hide()
|
|
||||||
if (isMac && hideDockWhenWinClose) {
|
|
||||||
app.dock.hide()
|
|
||||||
}
|
|
||||||
winIsHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showWin () {
|
|
||||||
if (win) {
|
|
||||||
win.show()
|
|
||||||
}
|
|
||||||
if (app.dock) {
|
|
||||||
app.dock.show()
|
|
||||||
}
|
|
||||||
winIsHidden = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeAppConfig (config) {
|
|
||||||
if (config.hideDockWhenWinClose != null) {
|
|
||||||
hideDockWhenWinClose = config.hideDockWhenWinClose
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWindow (startHideWindow) {
|
|
||||||
// Create the browser window.
|
|
||||||
const windowSize = DevSidecar.api.config.get().app.windowSize || {}
|
|
||||||
win = new BrowserWindow({
|
|
||||||
width: windowSize.width || 900,
|
|
||||||
height: windowSize.height || 750,
|
|
||||||
title: 'DevSidecar',
|
|
||||||
webPreferences: {
|
|
||||||
enableRemoteModule: true,
|
|
||||||
contextIsolation: false,
|
|
||||||
nativeWindowOpen: true, // ADD THIS
|
|
||||||
// preload: path.join(__dirname, 'preload.js'),
|
|
||||||
// Use pluginOptions.nodeIntegration, leave this alone
|
|
||||||
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
|
|
||||||
nodeIntegration: true, // process.env.ELECTRON_NODE_INTEGRATION
|
|
||||||
},
|
|
||||||
show: !startHideWindow,
|
|
||||||
icon: path.join(__static, 'icon.png'),
|
|
||||||
})
|
|
||||||
winIsHidden = !!startHideWindow
|
|
||||||
|
|
||||||
Menu.setApplicationMenu(null)
|
|
||||||
win.setMenu(null)
|
|
||||||
|
|
||||||
// !!IMPORTANT
|
|
||||||
if (isWindows && typeof _powerMonitor.setupMainWindow === 'function') {
|
|
||||||
_powerMonitor.setupMainWindow(win)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
|
||||||
// Load the url of the dev server if in development mode
|
|
||||||
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
|
||||||
if (!process.env.IS_TEST) {
|
|
||||||
setTimeout(openDevTools, 2000)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
createProtocol('app')
|
argv = process.argv
|
||||||
// Load the index.html when not in development
|
|
||||||
win.loadURL('app://./index.html')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startHideWindow) {
|
const cliProcess = fork(cli, argv, {
|
||||||
hideWin()
|
env: {
|
||||||
}
|
...process.env,
|
||||||
|
NO_CONSOLE_LOG: true,
|
||||||
win.on('closed', async (...args) => {
|
},
|
||||||
log.info('win closed:', ...args)
|
detached: true,
|
||||||
win = null
|
stdio: 'inherit',
|
||||||
tray = null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('close', async (event, message) => {
|
cliProcess.unref()
|
||||||
if (message.value === 1) {
|
|
||||||
quit()
|
|
||||||
} else {
|
|
||||||
hideWin()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
win.on('close', (e, ...args) => {
|
|
||||||
log.info('win close:', e, ...args)
|
|
||||||
if (forceClose) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.preventDefault()
|
|
||||||
if (isLinux()) {
|
|
||||||
quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const config = DevSidecar.api.config.get()
|
|
||||||
const closeStrategy = config.app.closeStrategy
|
|
||||||
if (closeStrategy === 0) {
|
|
||||||
// 弹窗提示,选择关闭策略
|
|
||||||
win.webContents.send('close.showTip', closeStrategy)
|
|
||||||
} else if (closeStrategy === 1) {
|
|
||||||
// 直接退出
|
|
||||||
quit()
|
|
||||||
} else if (closeStrategy === 2) {
|
|
||||||
// 隐藏窗口
|
|
||||||
hideWin()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
win.on('session-end', async (e, ...args) => {
|
|
||||||
log.info('win session-end:', e, ...args)
|
|
||||||
await quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
const shortcut = (event, input) => {
|
|
||||||
// 按 F12,打开/关闭 开发者工具
|
|
||||||
if (input.key === 'F12') {
|
|
||||||
// 阻止默认的按键事件行为
|
|
||||||
event.preventDefault()
|
|
||||||
// 切换开发者工具显示状态
|
|
||||||
switchDevTools()
|
|
||||||
// eslint-disable-next-line style/brace-style
|
|
||||||
}
|
|
||||||
// 按 F5,刷新页面
|
|
||||||
else if (input.key === 'F5') {
|
|
||||||
// 阻止默认的按键事件行为
|
|
||||||
event.preventDefault()
|
|
||||||
// 刷新页面
|
|
||||||
win.webContents.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听键盘事件
|
|
||||||
win.webContents.on('before-input-event', (event, input) => {
|
|
||||||
if (input.type !== 'keyUp' || input.control || input.alt || input.shift || input.meta) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
win.webContents.executeJavaScript('config')
|
|
||||||
.then((value) => {
|
|
||||||
console.info('window.config:', value, ', key:', input.key)
|
|
||||||
if (!value || (value.disableBeforeInputEvent !== true && value.disableBeforeInputEvent !== 'true')) {
|
|
||||||
shortcut(event, input)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
shortcut(event, input)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听渲染进程发送过来的消息
|
|
||||||
win.webContents.on('ipc-message', (event, channel, message, ...args) => {
|
|
||||||
console.info('win ipc-message:', event, channel, message, ...args)
|
|
||||||
if (channel === 'change-showHideShortcut') {
|
|
||||||
registerShowHideShortcut(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beforeQuit () {
|
async function startGUI () {
|
||||||
return DevSidecar.api.shutdown()
|
|
||||||
}
|
}
|
||||||
async function quit () {
|
|
||||||
if (tray) {
|
|
||||||
tray.displayBalloon({ title: '正在关闭', content: '关闭中,请稍候。。。' })
|
|
||||||
}
|
|
||||||
await beforeQuit()
|
|
||||||
forceClose = true
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerShowHideShortcut (showHideShortcut) {
|
|
||||||
globalShortcut.unregisterAll()
|
|
||||||
if (showHideShortcut && showHideShortcut !== '无' && showHideShortcut.length > 1) {
|
|
||||||
try {
|
|
||||||
const registerSuccess = globalShortcut.register(DevSidecar.api.config.get().app.showHideShortcut, () => {
|
|
||||||
if (winIsHidden || !win.isFocused()) {
|
|
||||||
if (!win.isFocused()) {
|
|
||||||
win.focus()
|
|
||||||
}
|
|
||||||
if (winIsHidden) {
|
|
||||||
showWin()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// linux,快捷键不关闭窗口
|
|
||||||
if (!isLinux()) {
|
|
||||||
hideWin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (registerSuccess) {
|
|
||||||
log.info('注册快捷键成功:', DevSidecar.api.config.get().app.showHideShortcut)
|
|
||||||
} else {
|
|
||||||
log.error('注册快捷键失败:', DevSidecar.api.config.get().app.showHideShortcut)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log.error('注册快捷键异常:', DevSidecar.api.config.get().app.showHideShortcut, ', error:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initApp () {
|
|
||||||
if (isMac) {
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
app.dock.setIcon(path.join(__dirname, '../build/mac/512x512.png'))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局监听快捷键,用于 显示/隐藏 窗口
|
|
||||||
app.whenReady().then(async () => {
|
|
||||||
registerShowHideShortcut(DevSidecar.api.config.get().app.showHideShortcut)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------执行开始---------------
|
|
||||||
app.disableHardwareAcceleration() // 禁用gpu
|
|
||||||
|
|
||||||
// 开启后是否默认隐藏window
|
|
||||||
let startHideWindow = !DevSidecar.api.config.get().app.startShowWindow
|
|
||||||
if (app.getLoginItemSettings().wasOpenedAsHidden) {
|
|
||||||
startHideWindow = true
|
|
||||||
} else if (process.argv) {
|
|
||||||
const args = minimist(process.argv)
|
|
||||||
log.info('start args:', args)
|
|
||||||
|
|
||||||
// 通过启动参数,判断是否隐藏窗口
|
|
||||||
const hideWindowArg = `${args.hideWindow}`
|
|
||||||
if (hideWindowArg === 'true' || hideWindowArg === '1') {
|
|
||||||
startHideWindow = true
|
|
||||||
} else if (hideWindowArg === 'false' || hideWindowArg === '0') {
|
|
||||||
startHideWindow = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info('startHideWindow = ', startHideWindow, ', app.getLoginItemSettings() = ', jsonApi.stringify2(app.getLoginItemSettings()))
|
|
||||||
|
|
||||||
// 禁止双开
|
|
||||||
const isFirstInstance = app.requestSingleInstanceLock()
|
|
||||||
if (!isFirstInstance) {
|
|
||||||
log.info('is second instance')
|
|
||||||
setTimeout(() => {
|
|
||||||
app.quit()
|
|
||||||
}, 1000)
|
|
||||||
} else {
|
|
||||||
app.on('before-quit', async () => {
|
|
||||||
log.info('before-quit')
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
app.on('will-quit', () => {
|
|
||||||
log.info('应用关闭,注销所有快捷键')
|
|
||||||
globalShortcut.unregisterAll()
|
|
||||||
})
|
|
||||||
app.on('second-instance', (event, commandLine) => {
|
|
||||||
log.info('new app started, command:', commandLine)
|
|
||||||
if (win) {
|
|
||||||
showWin()
|
|
||||||
win.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
log.info('window-all-closed')
|
|
||||||
// On macOS it is common for applications and their menu bar
|
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
// On macOS it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (win == null) {
|
|
||||||
createWindow(false)
|
|
||||||
} else {
|
|
||||||
showWin()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// initApp()
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
app.on('ready', async () => {
|
|
||||||
if (isDevelopment && !process.env.IS_TEST) {
|
|
||||||
// Install Vue Devtools
|
|
||||||
// try {
|
|
||||||
// await installExtension(VUEJS_DEVTOOLS)
|
|
||||||
// } catch (e) {
|
|
||||||
// log.error('Vue Devtools failed to install:', e.toString())
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
createWindow(startHideWindow)
|
|
||||||
const context = { win, app, beforeQuit, quit, ipcMain, dialog, log, api: DevSidecar.api, changeAppConfig }
|
|
||||||
backend.install(context) // 模块安装
|
|
||||||
} catch (err) {
|
|
||||||
log.info('error:', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 最小化到托盘
|
|
||||||
tray = setTray()
|
|
||||||
} catch (err) {
|
|
||||||
log.info('error:', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_powerMonitor.on('shutdown', async (e) => {
|
|
||||||
if (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
log.info('系统关机,恢复代理设置')
|
|
||||||
await quit()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
initApp()
|
|
||||||
|
|
||||||
// Exit cleanly on request from parent process in development mode.
|
|
||||||
if (isDevelopment) {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
process.on('message', (data) => {
|
|
||||||
if (data === 'graceful-exit') {
|
|
||||||
quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
process.on('SIGINT', () => {
|
|
||||||
quit()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 系统关机和重启时的操作
|
|
||||||
process.on('exit', () => {
|
|
||||||
log.info('进程结束,退出app')
|
|
||||||
quit()
|
|
||||||
})
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import DevSidecar from '@docmirror/dev-sidecar'
|
||||||
|
import { getExtraPath, mitmproxyPath } from './config'
|
||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import os from 'node:os'
|
||||||
|
import cac from 'cac'
|
||||||
|
|
||||||
|
const cli = cac('dev-sidecar')
|
||||||
|
const pk = require('../../package.json')
|
||||||
|
|
||||||
|
async function startup () {
|
||||||
|
console.log('启动 DevSidecar 服务')
|
||||||
|
const config = DevSidecar.api.config.get()
|
||||||
|
|
||||||
|
if (config.server.pid) {
|
||||||
|
process.kill(config.server.pid, os.constants.signals.SIGINT)
|
||||||
|
config.server.pid = null
|
||||||
|
DevSidecar.api.config.save(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启自动下载远程配置
|
||||||
|
await DevSidecar.api.config.startAutoDownloadRemoteConfig()
|
||||||
|
|
||||||
|
const { server } = await DevSidecar.api.server.start({
|
||||||
|
mitmproxyPath,
|
||||||
|
options: {
|
||||||
|
stdio: 'ignore',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 写入server进程pid
|
||||||
|
config.server.pid = server.id
|
||||||
|
DevSidecar.api.config.save(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stop () {
|
||||||
|
console.log('关闭 DevSidecar 服务')
|
||||||
|
const config = DevSidecar.api.config.get()
|
||||||
|
|
||||||
|
if (config.server.pid) {
|
||||||
|
process.kill(config.server.pid, os.constants.signals.SIGINT)
|
||||||
|
config.server.pid = null
|
||||||
|
DevSidecar.api.config.save(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restart () {
|
||||||
|
console.log('重启 DevSidecar 服务')
|
||||||
|
const config = DevSidecar.api.config.get()
|
||||||
|
|
||||||
|
if (config.server.pid) {
|
||||||
|
process.kill(config.server.pid, 'SIGINT')
|
||||||
|
}
|
||||||
|
|
||||||
|
await startup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const banner = fs.readFileSync(path.join(getExtraPath(), 'banner.txt'))
|
||||||
|
console.log(banner.toString())
|
||||||
|
|
||||||
|
cli
|
||||||
|
.help()
|
||||||
|
.usage('start')
|
||||||
|
.version(pk.version)
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('start', '启动 DevSidecar 服务')
|
||||||
|
.action(async () => {
|
||||||
|
await startup()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('stop', '关闭 DevSidecar 服务')
|
||||||
|
.action(stop)
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('restart', '重启 DevSidecar 服务')
|
||||||
|
.action(restart)
|
||||||
|
|
||||||
|
cli.parse()
|
|
@ -0,0 +1,93 @@
|
||||||
|
import path from 'node:path'
|
||||||
|
import DevSidecar from '@docmirror/dev-sidecar'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
|
const jsonApi = require('@docmirror/mitmproxy/src/json')
|
||||||
|
const log = require('../utils/util.log')
|
||||||
|
const configFromFiles = require('@docmirror/dev-sidecar/src/config/index.js').configFromFiles
|
||||||
|
|
||||||
|
export const mitmproxyPath = path.join(__dirname, 'mitmproxy.js')
|
||||||
|
process.env.DS_EXTRA_PATH = path.join(__dirname, '../extra/')
|
||||||
|
|
||||||
|
function getDefaultConfigBasePath () {
|
||||||
|
return DevSidecar.api.config.get().server.setting.userBasePath
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSettingsPath () {
|
||||||
|
const dir = getDefaultConfigBasePath()
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir)
|
||||||
|
} else {
|
||||||
|
// 兼容1.7.3及以下版本的配置文件处理逻辑
|
||||||
|
const newFilePath = path.join(dir, '/setting.json')
|
||||||
|
const oldFilePath = path.join(dir, '/setting.json5')
|
||||||
|
if (!fs.existsSync(newFilePath) && fs.existsSync(oldFilePath)) {
|
||||||
|
return oldFilePath // 如果新文件不存在,且旧文件存在,则返回旧文件路径
|
||||||
|
}
|
||||||
|
return newFilePath
|
||||||
|
}
|
||||||
|
return path.join(dir, '/setting.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogDir () {
|
||||||
|
return configFromFiles.app.logFileSavePath || path.join(getDefaultConfigBasePath(), '/logs/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadConfig () {
|
||||||
|
const settingPath = getSettingsPath()
|
||||||
|
let setting = {}
|
||||||
|
if (fs.existsSync(settingPath)) {
|
||||||
|
const file = fs.readFileSync(settingPath)
|
||||||
|
try {
|
||||||
|
setting = jsonApi.parse(file.toString())
|
||||||
|
log.info('读取 setting.json 成功:', settingPath)
|
||||||
|
} catch (e) {
|
||||||
|
log.error('读取 setting.json 失败:', settingPath, ', error:', e)
|
||||||
|
}
|
||||||
|
if (setting == null) {
|
||||||
|
setting = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (setting.overwall == null) {
|
||||||
|
setting.overwall = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting.installTime == null) {
|
||||||
|
// 设置安装时间
|
||||||
|
setting.installTime = getDateTimeStr()
|
||||||
|
|
||||||
|
// 初始化 rootCa.setuped
|
||||||
|
if (setting.rootCa == null) {
|
||||||
|
setting.rootCa = {
|
||||||
|
setuped: false,
|
||||||
|
desc: '根证书未安装',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存 setting.json
|
||||||
|
saveConfig(setting)
|
||||||
|
}
|
||||||
|
return setting
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveConfig (setting) {
|
||||||
|
const settingPath = getSettingsPath()
|
||||||
|
fs.writeFileSync(settingPath, jsonApi.stringify(setting))
|
||||||
|
log.info('保存 setting.json 配置文件成功:', settingPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDateTimeStr () {
|
||||||
|
const date = new Date() // 创建一个表示当前日期和时间的 Date 对象
|
||||||
|
const year = date.getFullYear() // 获取年份
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0') // 获取月份(注意月份从 0 开始计数)
|
||||||
|
const day = String(date.getDate()).padStart(2, '0') // 获取天数
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0') // 获取小时
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0') // 获取分钟
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0') // 获取秒数
|
||||||
|
const milliseconds = String(date.getMilliseconds()).padStart(3, '0') // 获取毫秒
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExtraPath () {
|
||||||
|
return path.join(__dirname, '../extra/')
|
||||||
|
}
|
|
@ -0,0 +1,493 @@
|
||||||
|
/* global __static */
|
||||||
|
|
||||||
|
import path from 'node:path'
|
||||||
|
import DevSidecar from '@docmirror/dev-sidecar'
|
||||||
|
import { app, BrowserWindow, dialog, globalShortcut, ipcMain, Menu, nativeImage, nativeTheme, powerMonitor, protocol, Tray } from 'electron'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||||
|
import backend from './bridge/backend'
|
||||||
|
import jsonApi from '@docmirror/mitmproxy/src/json'
|
||||||
|
import log from './utils/util.log'
|
||||||
|
|
||||||
|
DevSidecar.api.config.reload()
|
||||||
|
|
||||||
|
// Scheme must be registered before the app is ready
|
||||||
|
protocol.registerSchemesAsPrivileged([
|
||||||
|
{ scheme: 'app', privileges: { secure: true, standard: true } },
|
||||||
|
])
|
||||||
|
|
||||||
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
let win
|
||||||
|
let winIsHidden = false
|
||||||
|
|
||||||
|
let tray // 防止被内存清理
|
||||||
|
let forceClose = false
|
||||||
|
|
||||||
|
let hideDockWhenWinClose = DevSidecar.api.config.get().app.dock.hideWhenWinClose || false
|
||||||
|
|
||||||
|
const isWindows = process.platform === 'win32'
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
|
// 避免其他系统出现异常,只有 Windows 使用 './background/powerMonitor'
|
||||||
|
let _powerMonitor = powerMonitor
|
||||||
|
if (isWindows) {
|
||||||
|
try {
|
||||||
|
_powerMonitor = require('./background/powerMonitor').powerMonitor
|
||||||
|
} catch (e) {
|
||||||
|
log.error(`加载 './background/powerMonitor' 失败,现捕获异常并使用默认的 powerMonitor。\r\n目前,启动着DS重启电脑时,将无法正常关闭系统代理,届时请自行关闭系统代理!\r\n捕获的异常信息:`, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDevTools () {
|
||||||
|
try {
|
||||||
|
log.debug('尝试打开 `开发者工具`')
|
||||||
|
win.webContents.openDevTools()
|
||||||
|
log.debug('打开 `开发者工具` 成功')
|
||||||
|
} catch (e) {
|
||||||
|
log.error('打开 `开发者工具` 失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDevTools () {
|
||||||
|
try {
|
||||||
|
log.debug('尝试关闭 `开发者工具`')
|
||||||
|
win.webContents.closeDevTools()
|
||||||
|
log.debug('关闭 `开发者工具` 成功')
|
||||||
|
} catch (e) {
|
||||||
|
log.error('关闭 `开发者工具` 失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchDevTools () {
|
||||||
|
if (!win || !win.webContents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (win.webContents.isDevToolsOpened()) {
|
||||||
|
closeDevTools()
|
||||||
|
} else {
|
||||||
|
openDevTools()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏主窗口,并创建托盘,绑定关闭事件
|
||||||
|
function setTray () {
|
||||||
|
// const topMenu = Menu.buildFromTemplate({})
|
||||||
|
// Menu.setApplicationMenu(topMenu)
|
||||||
|
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
|
||||||
|
// 通常被添加到一个 context menu 上.
|
||||||
|
// 系统托盘右键菜单
|
||||||
|
const trayMenuTemplate = [
|
||||||
|
{
|
||||||
|
// 系统托盘图标目录
|
||||||
|
label: 'DevTools (F12)',
|
||||||
|
click: switchDevTools,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 系统托盘图标目录
|
||||||
|
label: '退出',
|
||||||
|
click: () => {
|
||||||
|
log.info('force quit')
|
||||||
|
forceClose = true
|
||||||
|
quit()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
// 设置系统托盘图标
|
||||||
|
const iconRootPath = path.join(__dirname, '../extra/icons/tray')
|
||||||
|
let iconPath = path.join(iconRootPath, 'icon.png')
|
||||||
|
const iconWhitePath = path.join(iconRootPath, 'icon-white.png')
|
||||||
|
const iconBlackPath = path.join(iconRootPath, 'icon-black.png')
|
||||||
|
if (isMac) {
|
||||||
|
iconPath = nativeTheme.shouldUseDarkColors ? iconWhitePath : iconBlackPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const trayIcon = nativeImage.createFromPath(iconPath)
|
||||||
|
const appTray = new Tray(trayIcon)
|
||||||
|
|
||||||
|
// 当桌面主题更新时
|
||||||
|
if (isMac) {
|
||||||
|
nativeTheme.on('updated', () => {
|
||||||
|
console.log('i am changed')
|
||||||
|
if (nativeTheme.shouldUseDarkColors) {
|
||||||
|
console.log('i am dark.')
|
||||||
|
tray.setImage(iconWhitePath)
|
||||||
|
} else {
|
||||||
|
console.log('i am light.')
|
||||||
|
tray.setImage(iconBlackPath)
|
||||||
|
// tray.setPressedImage(iconWhitePath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图标的上下文菜单
|
||||||
|
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
|
||||||
|
|
||||||
|
// 设置托盘悬浮提示
|
||||||
|
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
||||||
|
// 单击托盘小图标显示应用
|
||||||
|
appTray.on('click', () => {
|
||||||
|
// 显示主程序
|
||||||
|
showWin()
|
||||||
|
})
|
||||||
|
|
||||||
|
appTray.on('right-click', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
appTray.popUpContextMenu(contextMenu)
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
return appTray
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLinux () {
|
||||||
|
const platform = DevSidecar.api.shell.getSystemPlatform()
|
||||||
|
return platform === 'linux'
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideWin () {
|
||||||
|
if (win) {
|
||||||
|
if (isLinux()) {
|
||||||
|
quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
win.hide()
|
||||||
|
if (isMac && hideDockWhenWinClose) {
|
||||||
|
app.dock.hide()
|
||||||
|
}
|
||||||
|
winIsHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWin () {
|
||||||
|
if (win) {
|
||||||
|
win.show()
|
||||||
|
}
|
||||||
|
if (app.dock) {
|
||||||
|
app.dock.show()
|
||||||
|
}
|
||||||
|
winIsHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeAppConfig (config) {
|
||||||
|
if (config.hideDockWhenWinClose != null) {
|
||||||
|
hideDockWhenWinClose = config.hideDockWhenWinClose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow (startHideWindow) {
|
||||||
|
// Create the browser window.
|
||||||
|
const windowSize = DevSidecar.api.config.get().app.windowSize || {}
|
||||||
|
win = new BrowserWindow({
|
||||||
|
width: windowSize.width || 900,
|
||||||
|
height: windowSize.height || 750,
|
||||||
|
title: 'DevSidecar',
|
||||||
|
webPreferences: {
|
||||||
|
enableRemoteModule: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
nativeWindowOpen: true, // ADD THIS
|
||||||
|
// preload: path.join(__dirname, 'preload.js'),
|
||||||
|
// Use pluginOptions.nodeIntegration, leave this alone
|
||||||
|
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
|
||||||
|
nodeIntegration: true, // process.env.ELECTRON_NODE_INTEGRATION
|
||||||
|
},
|
||||||
|
show: !startHideWindow,
|
||||||
|
icon: path.join(__static, 'icon.png'),
|
||||||
|
})
|
||||||
|
winIsHidden = !!startHideWindow
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(null)
|
||||||
|
win.setMenu(null)
|
||||||
|
|
||||||
|
// !!IMPORTANT
|
||||||
|
if (isWindows && typeof _powerMonitor.setupMainWindow === 'function') {
|
||||||
|
_powerMonitor.setupMainWindow(win)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||||
|
// Load the url of the dev server if in development mode
|
||||||
|
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
||||||
|
if (!process.env.IS_TEST) {
|
||||||
|
setTimeout(openDevTools, 2000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createProtocol('app')
|
||||||
|
// Load the index.html when not in development
|
||||||
|
win.loadURL('app://./index.html')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startHideWindow) {
|
||||||
|
hideWin()
|
||||||
|
}
|
||||||
|
|
||||||
|
win.on('closed', async (...args) => {
|
||||||
|
log.info('win closed:', ...args)
|
||||||
|
win = null
|
||||||
|
tray = null
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('close', async (event, message) => {
|
||||||
|
if (message.value === 1) {
|
||||||
|
quit()
|
||||||
|
} else {
|
||||||
|
hideWin()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('close', (e, ...args) => {
|
||||||
|
log.info('win close:', e, ...args)
|
||||||
|
if (forceClose) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
if (isLinux()) {
|
||||||
|
quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const config = DevSidecar.api.config.get()
|
||||||
|
const closeStrategy = config.app.closeStrategy
|
||||||
|
if (closeStrategy === 0) {
|
||||||
|
// 弹窗提示,选择关闭策略
|
||||||
|
win.webContents.send('close.showTip', closeStrategy)
|
||||||
|
} else if (closeStrategy === 1) {
|
||||||
|
// 直接退出
|
||||||
|
quit()
|
||||||
|
} else if (closeStrategy === 2) {
|
||||||
|
// 隐藏窗口
|
||||||
|
hideWin()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('session-end', async (e, ...args) => {
|
||||||
|
log.info('win session-end:', e, ...args)
|
||||||
|
await quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
const shortcut = (event, input) => {
|
||||||
|
// 按 F12,打开/关闭 开发者工具
|
||||||
|
if (input.key === 'F12') {
|
||||||
|
// 阻止默认的按键事件行为
|
||||||
|
event.preventDefault()
|
||||||
|
// 切换开发者工具显示状态
|
||||||
|
switchDevTools()
|
||||||
|
// eslint-disable-next-line style/brace-style
|
||||||
|
}
|
||||||
|
// 按 F5,刷新页面
|
||||||
|
else if (input.key === 'F5') {
|
||||||
|
// 阻止默认的按键事件行为
|
||||||
|
event.preventDefault()
|
||||||
|
// 刷新页面
|
||||||
|
win.webContents.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听键盘事件
|
||||||
|
win.webContents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.type !== 'keyUp' || input.control || input.alt || input.shift || input.meta) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
win.webContents.executeJavaScript('config')
|
||||||
|
.then((value) => {
|
||||||
|
console.info('window.config:', value, ', key:', input.key)
|
||||||
|
if (!value || (value.disableBeforeInputEvent !== true && value.disableBeforeInputEvent !== 'true')) {
|
||||||
|
shortcut(event, input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
shortcut(event, input)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听渲染进程发送过来的消息
|
||||||
|
win.webContents.on('ipc-message', (event, channel, message, ...args) => {
|
||||||
|
console.info('win ipc-message:', event, channel, message, ...args)
|
||||||
|
if (channel === 'change-showHideShortcut') {
|
||||||
|
registerShowHideShortcut(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function beforeQuit () {
|
||||||
|
return DevSidecar.api.shutdown()
|
||||||
|
}
|
||||||
|
async function quit () {
|
||||||
|
if (tray) {
|
||||||
|
tray.displayBalloon({ title: '正在关闭', content: '关闭中,请稍候。。。' })
|
||||||
|
}
|
||||||
|
await beforeQuit()
|
||||||
|
forceClose = true
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerShowHideShortcut (showHideShortcut) {
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
if (showHideShortcut && showHideShortcut !== '无' && showHideShortcut.length > 1) {
|
||||||
|
try {
|
||||||
|
const registerSuccess = globalShortcut.register(DevSidecar.api.config.get().app.showHideShortcut, () => {
|
||||||
|
if (winIsHidden || !win.isFocused()) {
|
||||||
|
if (!win.isFocused()) {
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
if (winIsHidden) {
|
||||||
|
showWin()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// linux,快捷键不关闭窗口
|
||||||
|
if (!isLinux()) {
|
||||||
|
hideWin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (registerSuccess) {
|
||||||
|
log.info('注册快捷键成功:', DevSidecar.api.config.get().app.showHideShortcut)
|
||||||
|
} else {
|
||||||
|
log.error('注册快捷键失败:', DevSidecar.api.config.get().app.showHideShortcut)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error('注册快捷键异常:', DevSidecar.api.config.get().app.showHideShortcut, ', error:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initApp () {
|
||||||
|
if (isMac) {
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
app.dock.setIcon(path.join(__dirname, '../build/mac/512x512.png'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局监听快捷键,用于 显示/隐藏 窗口
|
||||||
|
app.whenReady().then(async () => {
|
||||||
|
registerShowHideShortcut(DevSidecar.api.config.get().app.showHideShortcut)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------执行开始---------------
|
||||||
|
app.disableHardwareAcceleration() // 禁用gpu
|
||||||
|
|
||||||
|
// 开启后是否默认隐藏window
|
||||||
|
let startHideWindow = !DevSidecar.api.config.get().app.startShowWindow
|
||||||
|
if (app.getLoginItemSettings().wasOpenedAsHidden) {
|
||||||
|
startHideWindow = true
|
||||||
|
} else if (process.argv) {
|
||||||
|
const args = minimist(process.argv)
|
||||||
|
log.info('start args:', args)
|
||||||
|
|
||||||
|
// 通过启动参数,判断是否隐藏窗口
|
||||||
|
const hideWindowArg = `${args.hideWindow}`
|
||||||
|
if (hideWindowArg === 'true' || hideWindowArg === '1') {
|
||||||
|
startHideWindow = true
|
||||||
|
} else if (hideWindowArg === 'false' || hideWindowArg === '0') {
|
||||||
|
startHideWindow = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info('startHideWindow = ', startHideWindow, ', app.getLoginItemSettings() = ', jsonApi.stringify2(app.getLoginItemSettings()))
|
||||||
|
|
||||||
|
// 禁止双开
|
||||||
|
const isFirstInstance = app.requestSingleInstanceLock()
|
||||||
|
if (!isFirstInstance) {
|
||||||
|
log.info('is second instance')
|
||||||
|
setTimeout(() => {
|
||||||
|
app.quit()
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
app.on('before-quit', async () => {
|
||||||
|
log.info('before-quit')
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.on('will-quit', () => {
|
||||||
|
log.info('应用关闭,注销所有快捷键')
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
})
|
||||||
|
app.on('second-instance', (event, commandLine) => {
|
||||||
|
log.info('new app started, command:', commandLine)
|
||||||
|
if (win) {
|
||||||
|
showWin()
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
log.info('window-all-closed')
|
||||||
|
// On macOS it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (win == null) {
|
||||||
|
createWindow(false)
|
||||||
|
} else {
|
||||||
|
showWin()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// initApp()
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', async () => {
|
||||||
|
if (isDevelopment && !process.env.IS_TEST) {
|
||||||
|
// Install Vue Devtools
|
||||||
|
// try {
|
||||||
|
// await installExtension(VUEJS_DEVTOOLS)
|
||||||
|
// } catch (e) {
|
||||||
|
// log.error('Vue Devtools failed to install:', e.toString())
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
createWindow(startHideWindow)
|
||||||
|
const context = { win, app, beforeQuit, quit, ipcMain, dialog, log, api: DevSidecar.api, changeAppConfig }
|
||||||
|
backend.install(context) // 模块安装
|
||||||
|
} catch (err) {
|
||||||
|
log.info('error:', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 最小化到托盘
|
||||||
|
tray = setTray()
|
||||||
|
} catch (err) {
|
||||||
|
log.info('error:', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_powerMonitor.on('shutdown', async (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
log.info('系统关机,恢复代理设置')
|
||||||
|
await quit()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
initApp()
|
||||||
|
|
||||||
|
// Exit cleanly on request from parent process in development mode.
|
||||||
|
if (isDevelopment) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
process.on('message', (data) => {
|
||||||
|
if (data === 'graceful-exit') {
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
quit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 系统关机和重启时的操作
|
||||||
|
process.on('exit', () => {
|
||||||
|
log.info('进程结束,退出app')
|
||||||
|
quit()
|
||||||
|
})
|
|
@ -1,33 +1,11 @@
|
||||||
import fs from 'node:fs'
|
|
||||||
import path from 'node:path'
|
|
||||||
import DevSidecar from '@docmirror/dev-sidecar'
|
import DevSidecar from '@docmirror/dev-sidecar'
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
|
import { getDateTimeStr, getDefaultConfigBasePath, getLogDir, loadConfig, mitmproxyPath, saveConfig } from '../../background/config'
|
||||||
|
|
||||||
const jsonApi = require('@docmirror/mitmproxy/src/json')
|
|
||||||
const pk = require('../../../package.json')
|
const pk = require('../../../package.json')
|
||||||
const configFromFiles = require('@docmirror/dev-sidecar/src/config/index.js').configFromFiles
|
|
||||||
const log = require('../../utils/util.log')
|
const log = require('../../utils/util.log')
|
||||||
|
|
||||||
const mitmproxyPath = path.join(__dirname, 'mitmproxy.js')
|
|
||||||
process.env.DS_EXTRA_PATH = path.join(__dirname, '../extra/')
|
|
||||||
|
|
||||||
const getDefaultConfigBasePath = function () {
|
|
||||||
return DevSidecar.api.config.get().server.setting.userBasePath
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDateTimeStr = function () {
|
|
||||||
const date = new Date() // 创建一个表示当前日期和时间的 Date 对象
|
|
||||||
const year = date.getFullYear() // 获取年份
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0') // 获取月份(注意月份从 0 开始计数)
|
|
||||||
const day = String(date.getDate()).padStart(2, '0') // 获取天数
|
|
||||||
const hours = String(date.getHours()).padStart(2, '0') // 获取小时
|
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0') // 获取分钟
|
|
||||||
const seconds = String(date.getSeconds()).padStart(2, '0') // 获取秒数
|
|
||||||
const milliseconds = String(date.getMilliseconds()).padStart(3, '0') // 获取毫秒
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const localApi = {
|
const localApi = {
|
||||||
/**
|
/**
|
||||||
* 返回所有api列表,供vue来ipc调用
|
* 返回所有api列表,供vue来ipc调用
|
||||||
|
@ -52,7 +30,7 @@ const localApi = {
|
||||||
return getDefaultConfigBasePath()
|
return getDefaultConfigBasePath()
|
||||||
},
|
},
|
||||||
getLogDir () {
|
getLogDir () {
|
||||||
return configFromFiles.app.logFileSavePath || path.join(getDefaultConfigBasePath(), '/logs/')
|
return getLogDir()
|
||||||
},
|
},
|
||||||
getSystemPlatform (throwIfUnknown = false) {
|
getSystemPlatform (throwIfUnknown = false) {
|
||||||
return DevSidecar.api.shell.getSystemPlatform(throwIfUnknown)
|
return DevSidecar.api.shell.getSystemPlatform(throwIfUnknown)
|
||||||
|
@ -63,45 +41,10 @@ const localApi = {
|
||||||
*/
|
*/
|
||||||
setting: {
|
setting: {
|
||||||
load () {
|
load () {
|
||||||
const settingPath = _getSettingsPath()
|
return loadConfig()
|
||||||
let setting = {}
|
|
||||||
if (fs.existsSync(settingPath)) {
|
|
||||||
const file = fs.readFileSync(settingPath)
|
|
||||||
try {
|
|
||||||
setting = jsonApi.parse(file.toString())
|
|
||||||
log.info('读取 setting.json 成功:', settingPath)
|
|
||||||
} catch (e) {
|
|
||||||
log.error('读取 setting.json 失败:', settingPath, ', error:', e)
|
|
||||||
}
|
|
||||||
if (setting == null) {
|
|
||||||
setting = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (setting.overwall == null) {
|
|
||||||
setting.overwall = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting.installTime == null) {
|
|
||||||
// 设置安装时间
|
|
||||||
setting.installTime = getDateTimeStr()
|
|
||||||
|
|
||||||
// 初始化 rootCa.setuped
|
|
||||||
if (setting.rootCa == null) {
|
|
||||||
setting.rootCa = {
|
|
||||||
setuped: false,
|
|
||||||
desc: '根证书未安装',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存 setting.json
|
|
||||||
localApi.setting.save(setting)
|
|
||||||
}
|
|
||||||
return setting
|
|
||||||
},
|
},
|
||||||
save (setting = {}) {
|
save (setting = {}) {
|
||||||
const settingPath = _getSettingsPath()
|
saveConfig(setting)
|
||||||
fs.writeFileSync(settingPath, jsonApi.stringify(setting))
|
|
||||||
log.info('保存 setting.json 配置文件成功:', settingPath)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -140,22 +83,6 @@ function _deepFindFunction (list, parent, parentKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getSettingsPath () {
|
|
||||||
const dir = getDefaultConfigBasePath()
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
fs.mkdirSync(dir)
|
|
||||||
} else {
|
|
||||||
// 兼容1.7.3及以下版本的配置文件处理逻辑
|
|
||||||
const newFilePath = path.join(dir, '/setting.json')
|
|
||||||
const oldFilePath = path.join(dir, '/setting.json5')
|
|
||||||
if (!fs.existsSync(newFilePath) && fs.existsSync(oldFilePath)) {
|
|
||||||
return oldFilePath // 如果新文件不存在,且旧文件存在,则返回旧文件路径
|
|
||||||
}
|
|
||||||
return newFilePath
|
|
||||||
}
|
|
||||||
return path.join(dir, '/setting.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
function invoke (api, param) {
|
function invoke (api, param) {
|
||||||
let target = lodash.get(localApi, api)
|
let target = lodash.get(localApi, api)
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
|
|
|
@ -122,6 +122,7 @@ module.exports = defineConfig({
|
||||||
},
|
},
|
||||||
chainWebpackMainProcess (config) {
|
chainWebpackMainProcess (config) {
|
||||||
config.entry('mitmproxy').add(path.join(__dirname, 'src/bridge/mitmproxy.js'))
|
config.entry('mitmproxy').add(path.join(__dirname, 'src/bridge/mitmproxy.js'))
|
||||||
|
config.entry('dev-sidecar-cli').add(path.join(__dirname, 'src/background/cli.js'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -87,6 +87,9 @@ importers:
|
||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: ^1.7.8
|
specifier: ^1.7.8
|
||||||
version: 1.7.8(vue-template-compiler@2.7.16)(vue@2.7.16)
|
version: 1.7.8(vue-template-compiler@2.7.16)(vue@2.7.16)
|
||||||
|
cac:
|
||||||
|
specifier: ^6.7.14
|
||||||
|
version: 6.7.14
|
||||||
electron-baidu-tongji:
|
electron-baidu-tongji:
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
|
@ -133,6 +136,9 @@ importers:
|
||||||
'@vue/cli-service':
|
'@vue/cli-service':
|
||||||
specifier: ^5.0.8
|
specifier: ^5.0.8
|
||||||
version: 5.0.8(@vue/compiler-sfc@3.5.12)(ejs@3.1.10)(encoding@0.1.13)(handlebars@4.7.8)(lodash@4.17.21)(sass-loader@16.0.3(sass@1.81.0)(webpack@5.96.1))(underscore@1.13.7)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
|
version: 5.0.8(@vue/compiler-sfc@3.5.12)(ejs@3.1.10)(encoding@0.1.13)(handlebars@4.7.8)(lodash@4.17.21)(sass-loader@16.0.3(sass@1.81.0)(webpack@5.96.1))(underscore@1.13.7)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
|
||||||
|
cross-env:
|
||||||
|
specifier: ^7.0.3
|
||||||
|
version: 7.0.3
|
||||||
electron:
|
electron:
|
||||||
specifier: ^19.1.9
|
specifier: ^19.1.9
|
||||||
version: 19.1.9
|
version: 19.1.9
|
||||||
|
@ -2239,6 +2245,10 @@ packages:
|
||||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
cac@6.7.14:
|
||||||
|
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
cacache@16.1.3:
|
cacache@16.1.3:
|
||||||
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
|
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
|
||||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||||
|
@ -2771,6 +2781,11 @@ packages:
|
||||||
crc@3.8.0:
|
crc@3.8.0:
|
||||||
resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
|
resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
|
||||||
|
|
||||||
|
cross-env@7.0.3:
|
||||||
|
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||||
|
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
cross-spawn@4.0.2:
|
cross-spawn@4.0.2:
|
||||||
resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==}
|
resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==}
|
||||||
|
|
||||||
|
@ -9841,6 +9856,8 @@ snapshots:
|
||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
|
cac@6.7.14: {}
|
||||||
|
|
||||||
cacache@16.1.3:
|
cacache@16.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@npmcli/fs': 2.1.2
|
'@npmcli/fs': 2.1.2
|
||||||
|
@ -10262,6 +10279,10 @@ snapshots:
|
||||||
buffer: 5.7.1
|
buffer: 5.7.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
cross-env@7.0.3:
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: 7.0.5
|
||||||
|
|
||||||
cross-spawn@4.0.2:
|
cross-spawn@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 4.1.5
|
lru-cache: 4.1.5
|
||||||
|
|
Loading…
Reference in New Issue