Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions _script/dev.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#Requires -Version 5.1
<#
dev-sidecar 开发环境统一启动/检查脚本

用法(在仓库根目录或任意目录执行):
pwsh -File _script\dev.ps1 -Action start # 启动开发环境并等待端口就绪
pwsh -File _script\dev.ps1 -Action check # 检查端口监听状态
pwsh -File _script\dev.ps1 -Action stop # 停止开发环境(按端口结束进程)
pwsh -File _script\dev.ps1 -Action restart # 停止后重新启动

可选参数:
-Port 8081 # 前端 dev server 端口,默认 8081
-Foreground # start 时在前台运行,日志直接输出到当前终端(Ctrl+C 停止)
-SkipKill # start/restart 时不先结束已占用端口的进程
#>
param(
[ValidateSet('start', 'check', 'stop', 'restart')]
[string]$Action = 'start',

[int]$Port = 8081,

[switch]$Foreground,

[switch]$SkipKill
)

$ErrorActionPreference = 'SilentlyContinue'

$repoRoot = Split-Path -Parent $PSScriptRoot
$guiDir = Join-Path $repoRoot 'packages\gui'
$electronDev = Join-Path $PSScriptRoot 'electron-dev.mjs'
$proxyHttpPort = 31180
$proxyHttpsPort = 31181
$ports = @($Port, $proxyHttpPort, $proxyHttpsPort)

function Get-Listeners([int[]]$TargetPorts) {
Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
Where-Object { $TargetPorts -contains $_.LocalPort }
}

function Show-Status([string]$Title) {
Write-Host "== $Title =="
$listeners = @(Get-Listeners $ports)
if ($listeners.Count -gt 0) {
$listeners | Sort-Object LocalPort | ForEach-Object {
$procName = (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName
Write-Host (" {0}:{1,-6} PID {2,-7} {3}" -f $_.LocalAddress, $_.LocalPort, $_.OwningProcess, $procName)
}
} else {
Write-Host ' no listeners'
}
Write-Host ''
}

function Stop-DevSidecar([int[]]$TargetPorts) {
Write-Host '== stopping dev-sidecar =='
$listeners = @(Get-Listeners $TargetPorts)
$procIds = @($listeners | Select-Object -ExpandProperty OwningProcess -Unique)
if ($procIds.Count -eq 0) {
Write-Host ' no listeners'
} else {
foreach ($procId in $procIds) {
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
Write-Host " killed PID $procId"
}
}
Start-Sleep -Milliseconds 600
Write-Host ''
}

function Wait-Ports([int[]]$TargetPorts, [int]$TimeoutSec = 120) {
$deadline = (Get-Date).AddSeconds($TimeoutSec)
$ready = @{}

while ((Get-Date) -lt $deadline) {
$listeners = @(Get-Listeners $TargetPorts)
foreach ($p in $TargetPorts) {
if ($listeners.LocalPort -contains $p) {
$ready[$p] = $true
}
}

if ($ready.Count -ge $TargetPorts.Count) {
break
}

Start-Sleep -Seconds 1
}

if ($ready.Count -lt $TargetPorts.Count) {
$missing = @($TargetPorts | Where-Object { -not $ready.ContainsKey($_) })
Write-Host "等待端口超时,未就绪端口: $($missing -join ', ')"
} else {
Write-Host "端口已全部就绪: $($TargetPorts -join ', ')"
}
}

function Start-DevSidecar([int]$DevPort) {
$node = (Get-Command node -ErrorAction Stop).Source

if ($Foreground) {
Write-Host "== starting dev-sidecar in foreground (Ctrl+C to stop) =="
Push-Location $guiDir
try {
& $node $electronDev '--port' "$DevPort"
} finally {
Pop-Location
}
return
}

Write-Host "== starting dev-sidecar =="
$proc = Start-Process -FilePath $node -ArgumentList @($electronDev, '--port', "$DevPort") -WorkingDirectory $guiDir -WindowStyle Hidden -PassThru
Write-Host " launcher PID: $($proc.Id)"
Wait-Ports $ports
Show-Status 'port status after start'
}

switch ($Action) {
'check' {
Show-Status "port status (dev: $Port, http proxy: $proxyHttpPort, https proxy: $proxyHttpsPort)"
break
}

'stop' {
Stop-DevSidecar $ports
Show-Status 'port status after stop'
break
}

'start' {
if (-not $SkipKill) {
Stop-DevSidecar $ports
}
Start-DevSidecar $Port
break
}

'restart' {
Stop-DevSidecar $ports
Start-DevSidecar $Port
break
}
}
14 changes: 12 additions & 2 deletions _script/electron-dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ import { setTimeout as delay } from 'node:timers/promises'
const guiDir = process.cwd()
const require = createRequire(import.meta.url)

const devServerUrl = 'http://localhost:8080'
function resolveDevServer () {
const argv = process.argv.slice(2)
const portIndex = argv.indexOf('--port')
const port = portIndex >= 0 ? Number.parseInt(argv[portIndex + 1], 10) : 8080
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
throw new Error(`无效的端口号: ${argv[portIndex + 1]}`)
}
return { port, url: `http://localhost:${port}` }
}

const { port: devServerPort, url: devServerUrl } = resolveDevServer()
const state = {
closing: false,
devServer: null,
Expand Down Expand Up @@ -86,7 +96,7 @@ async function main () {
const vueCliServiceBin = resolveVueCliServiceBin()
const electronBin = resolveElectronBin()

state.devServer = spawnCommand(process.execPath, [vueCliServiceBin, 'serve', '--port', '8080'])
state.devServer = spawnCommand(process.execPath, [vueCliServiceBin, 'serve', '--port', String(devServerPort)])
state.devServer.on('exit', (code, signal) => {
if (!state.closing) {
void shutdown(code ?? (signal ? 1 : 0))
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/config-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ const configApi = {
if (remoteConfigUrl.startsWith('https://raw.githubusercontent.com/')) {
headers['Server-Name'] = 'baidu.com'
}
request(remoteConfigUrl, { headers }, (error, response, body) => {
// 禁用环境变量代理(HTTPS_PROXY/HTTP_PROXY):
// 当用户开启 proxy.setEnv 后,环境变量会指向 dev-sidecar 自己的代理端口,
// 启动时本地代理尚未监听,走代理会导致下载失败(ECONNREFUSED 127.0.0.1:31181),
// 新装用户会因此无法下载远程配置,只能使用内置规则。
request(remoteConfigUrl, { headers, proxy: null }, (error, response, body) => {
if (error) {
log.error(`下载远程配置失败: ${remoteConfigUrl}, error:`, error, ', response:', response, ', body:', body)
reject(error)
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const defaultConfig = {
showShutdownTip: true,

// 日志相关配置
logDisabled: false, // 完全禁用日志:控制台不输出,日志文件也不写入
logFileSavePath: path.join(configLoader.getUserBasePath(), '/logs'), // 日志文件保存路径
keepLogFileCount: 15, // 保留日志文件数
maxLogFileSize: 1, // 最大日志文件大小
Expand Down Expand Up @@ -99,6 +100,11 @@ const defaultConfig = {
// }
},
},
// Cloudflare 路由重定向:命中 Cloudflare IP 段时改写为优选地址
cloudflareRoute: {
enabled: false,
preferredEndpoint: '', // 优选地址,可填写 IP 或 CNAME 域名
},
intercept: {
enabled: true,
},
Expand Down
82 changes: 49 additions & 33 deletions packages/core/src/expose.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,80 +55,96 @@ config.resetDefault()
const server = modules.server
const serverStart = server.start

function newServerStart ({ mitmproxyPath }) {
return serverStart({ mitmproxyPath, plugins: plugin })
function newServerStart ({ mitmproxyPath, setting }) {
return serverStart({ mitmproxyPath, plugins: plugin, setting })
}
server.start = newServerStart
async function startup ({ mitmproxyPath }) {
async function startup ({ mitmproxyPath, setting }) {
const conf = config.get()
const tasks = []

if (conf.server.enabled && !status.server.enabled) {
try {
await server.start({ mitmproxyPath })
} catch (err) {
log.error('代理服务启动失败:', err)
}
tasks.push((async () => {
try {
await server.start({ mitmproxyPath })
} catch (err) {
log.error('代理服务启动失败:', err)
}
})())
}
if (conf.proxy.enabled && !status.proxy.enabled) {
try {
await proxy.start()
} catch (err) {
log.error('开启系统代理失败:', err)
}
tasks.push((async () => {
try {
await proxy.start()
} catch (err) {
log.error('开启系统代理失败:', err)
}
})())
}

try {
const plugins = []
for (const key in plugin) {
if (conf.plugin[key].enabled && !status.plugin[key]?.enabled) {
const start = async () => {
if (key === 'overwall' && setting && setting.overwall !== true) {
log.info(`插件【${key}】未启动:setting.json 未开启 overwall`)
continue
}
tasks.push((async () => {
try {
await plugin[key].start()
log.info(`插件【${key}】已启动`)
} catch (err) {
log.error(`插件【${key}】启动失败:`, err)
}
}
plugins.push(start())
})())
}
}
if (plugins && plugins.length > 0) {
await Promise.all(plugins)
}
} catch (err) {
log.error('开启插件失败:', err)
}

if (tasks.length > 0) {
// server、系统代理、各插件之间没有相互依赖,并行启动以缩短整体等待时间
await Promise.all(tasks)
}
}

async function shutdown () {
const tasks = []

try {
const plugins = []
for (const key in plugin) {
if (status.plugin[key] && status.plugin[key].enabled && plugin[key].close) {
const close = async () => {
tasks.push((async () => {
try {
await plugin[key].close()
log.info(`插件【${key}】已关闭`)
} catch (err) {
log.error(`插件【${key}】关闭失败:`, err)
}
}
plugins.push(close())
})())
}
}
if (plugins.length > 0) {
await Promise.all(plugins)
}
} catch (error) {
log.error('插件关闭失败:', error)
}

if (status.proxy.enabled) {
try {
await proxy.close()
log.info('系统代理已关闭')
} catch (err) {
log.error('系统代理关闭失败:', err)
}
tasks.push((async () => {
try {
await proxy.close()
log.info('系统代理已关闭')
} catch (err) {
log.error('系统代理关闭失败:', err)
}
})())
}

if (tasks.length > 0) {
// 插件关闭(清理 git/npm 配置)与关闭系统代理互不依赖,并行执行
await Promise.all(tasks)
}

if (status.server.enabled) {
try {
await server.close()
Expand Down
13 changes: 7 additions & 6 deletions packages/core/src/modules/plugin/node/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ module.exports = {
},
},
},
variables: {
phantomjs_cdnurl: 'https://npmmirror.com/mirrors/phantomjs',
chromedriver_cdnurl: 'https://npmmirror.com/mirrors/chromedriver',
sass_binary_site: 'https://npmmirror.com/mirrors/node-sass',
// npm 11 开始不再接受未知的 npm config,以下镜像变量全部改为直接设置系统环境变量
variables: {},
envVariables: {
ELECTRON_MIRROR: 'https://npmmirror.com/mirrors/electron/',
NVM_NODEJS_ORG_MIRROR: 'https://npmmirror.com/mirrors/node',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npmmirror.com/mirrors/electron-builder-binaries/',
PHANTOMJS_CDNURL: 'https://npmmirror.com/mirrors/phantomjs',
CHROMEDRIVER_CDNURL: 'https://npmmirror.com/mirrors/chromedriver',
SASS_BINARY_SITE: 'https://npmmirror.com/mirrors/node-sass',
NVM_NODEJS_ORG_MIRROR: 'https://npmmirror.com/mirrors/node',
OPERADRIVER: 'https://npmmirror.com/mirrors/operadriver',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npmmirror.com/mirrors/electron-builder-binaries/',
PYTHON_MIRROR: 'https://npmmirror.com/mirrors/python',
},
}
Loading
Loading