mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # README.md
This commit is contained in:
@@ -225,6 +225,18 @@ data/automation/scripts/<script-id>/
|
||||
|
||||
这里的 `config` 是应用内部持久化格式;对外复制、导入、脚本库管理一律使用 `automation.script.json` 包结构。
|
||||
|
||||
### Windows 发布打包(源码)
|
||||
|
||||
Windows 发布脚本默认保持原有 NSIS 安装包行为,也可以生成便携 ZIP,或一次生成两种产物:
|
||||
|
||||
```powershell
|
||||
bat\publish.bat -Target WINDOWS -WindowsFormat INSTALLER
|
||||
bat\publish.bat -Target WINDOWS -WindowsFormat PORTABLE
|
||||
bat\publish.bat -Target WINDOWS -WindowsFormat BOTH
|
||||
```
|
||||
|
||||
省略 `-WindowsFormat` 时等同于 `INSTALLER`。安装包和便携 ZIP 输出到 `publish\output\`。
|
||||
|
||||
### Linux 发布打包(源码)
|
||||
|
||||
Linux 发布脚本位于 `publish/linux/`。
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"ant-chrome/backend/internal/browser"
|
||||
"ant-chrome/backend/internal/config"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type browserCoreDAOStub struct {
|
||||
list []browser.Core
|
||||
}
|
||||
|
||||
func (s *browserCoreDAOStub) List() ([]browser.Core, error) {
|
||||
return append([]browser.Core{}, s.list...), nil
|
||||
}
|
||||
|
||||
func (s *browserCoreDAOStub) Upsert(core browser.Core) error {
|
||||
for i := range s.list {
|
||||
if s.list[i].CoreId == core.CoreId {
|
||||
s.list[i] = core
|
||||
return nil
|
||||
}
|
||||
}
|
||||
s.list = append(s.list, core)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *browserCoreDAOStub) Delete(coreId string) error {
|
||||
next := s.list[:0]
|
||||
for _, core := range s.list {
|
||||
if core.CoreId != coreId {
|
||||
next = append(next, core)
|
||||
}
|
||||
}
|
||||
s.list = next
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *browserCoreDAOStub) SetDefault(coreId string) error {
|
||||
for i := range s.list {
|
||||
s.list[i].IsDefault = s.list[i].CoreId == coreId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBrowserCoreScanRegistersDetectedCoreInDAO(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
coreDir := filepath.Join(root, "chrome", "chromium-148")
|
||||
exePath := filepath.Join(coreDir, filepath.FromSlash(browser.CoreExecutableCandidates()[0]))
|
||||
if err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {
|
||||
t.Fatalf("创建测试内核目录失败: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(exePath, []byte("stub"), 0o755); err != nil {
|
||||
t.Fatalf("写入测试内核可执行文件失败: %v", err)
|
||||
}
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Browser.Cores = nil
|
||||
|
||||
app := NewApp(root)
|
||||
app.config = cfg
|
||||
app.browserMgr = browser.NewManager(cfg, root)
|
||||
dao := &browserCoreDAOStub{}
|
||||
app.browserMgr.CoreDAO = dao
|
||||
|
||||
cores := app.BrowserCoreScan()
|
||||
if len(dao.list) != 1 {
|
||||
t.Fatalf("扫描后应写入 DAO 1 个内核,got=%d", len(dao.list))
|
||||
}
|
||||
if dao.list[0].CoreId != "core-chromium-148" {
|
||||
t.Fatalf("内核 ID 不符合预期: got=%q", dao.list[0].CoreId)
|
||||
}
|
||||
if dao.list[0].CorePath != filepath.Join("chrome", "chromium-148") {
|
||||
t.Fatalf("内核路径不符合预期: got=%q", dao.list[0].CorePath)
|
||||
}
|
||||
if !dao.list[0].IsDefault {
|
||||
t.Fatal("首个扫描到的内核应设为默认")
|
||||
}
|
||||
if len(cores) != 1 || cores[0].CoreId != dao.list[0].CoreId {
|
||||
t.Fatalf("扫描返回列表未包含新内核: %+v", cores)
|
||||
}
|
||||
}
|
||||
+58
-3
@@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -58,7 +59,7 @@ func (a *App) ensureDefaultCores() {
|
||||
log := logger.New("Browser")
|
||||
|
||||
// 扫描 chrome/ 目录,无论配置是否已有内核都执行一次,确保新增子目录被发现
|
||||
detected := a.scanChromeDir("chrome")
|
||||
detected := a.scanChromeDir(a.browserCoreRoot())
|
||||
|
||||
if len(a.config.Browser.Cores) == 0 {
|
||||
// 配置为空:直接用扫描结果,或兜底写一个占位
|
||||
@@ -100,9 +101,8 @@ func (a *App) ensureDefaultCores() {
|
||||
|
||||
func (a *App) autoDetectCores() {
|
||||
log := logger.New("Browser")
|
||||
// ensureDefaultCores 已完成扫描注册,这里只做路径有效性日志。
|
||||
cores := a.scanAndRegisterCores()
|
||||
// SQLite 模式下以内核表为准,避免与 config.yaml 历史条目不一致。
|
||||
cores := a.config.Browser.Cores
|
||||
if a.browserMgr != nil {
|
||||
cores = a.browserMgr.ListCores()
|
||||
}
|
||||
@@ -116,6 +116,61 @@ func (a *App) autoDetectCores() {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) browserCoreRoot() string {
|
||||
if a != nil && a.config != nil {
|
||||
if root := strings.TrimSpace(a.config.Browser.CoreRoot); root != "" {
|
||||
return root
|
||||
}
|
||||
}
|
||||
return "chrome"
|
||||
}
|
||||
|
||||
func (a *App) scanAndRegisterCores() []browser.Core {
|
||||
log := logger.New("Browser")
|
||||
detected := a.scanChromeDir(a.browserCoreRoot())
|
||||
if len(detected) == 0 || a.browserMgr == nil {
|
||||
return detected
|
||||
}
|
||||
|
||||
existing := a.browserMgr.ListCores()
|
||||
knownPaths := make(map[string]struct{}, len(existing))
|
||||
hasDefault := false
|
||||
for _, core := range existing {
|
||||
knownPaths[normalizeCorePathForCompare(core.CorePath)] = struct{}{}
|
||||
if core.IsDefault {
|
||||
hasDefault = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, core := range detected {
|
||||
if _, ok := knownPaths[normalizeCorePathForCompare(core.CorePath)]; ok {
|
||||
continue
|
||||
}
|
||||
core.IsDefault = !hasDefault
|
||||
if err := a.browserMgr.SaveCore(browser.CoreInput{
|
||||
CoreId: core.CoreId,
|
||||
CoreName: core.CoreName,
|
||||
CorePath: core.CorePath,
|
||||
IsDefault: core.IsDefault,
|
||||
}); err != nil {
|
||||
log.Warn("自动注册内核失败", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath), logger.F("error", err.Error()))
|
||||
continue
|
||||
}
|
||||
log.Info("发现新内核,已注册", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath))
|
||||
knownPaths[normalizeCorePathForCompare(core.CorePath)] = struct{}{}
|
||||
hasDefault = hasDefault || core.IsDefault
|
||||
}
|
||||
return a.browserMgr.ListCores()
|
||||
}
|
||||
|
||||
func normalizeCorePathForCompare(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.ToSlash(filepath.Clean(path))
|
||||
}
|
||||
|
||||
// scanChromeDir 扫描指定目录,将包含浏览器可执行文件的子文件夹识别为内核。
|
||||
// 如果目录本身包含可执行文件(旧版单内核结构),则直接返回该目录作为内核。
|
||||
func (a *App) scanChromeDir(chromeRoot string) []browser.Core {
|
||||
|
||||
+130
-19
@@ -1,6 +1,8 @@
|
||||
param(
|
||||
[string]$Target,
|
||||
[string]$Version
|
||||
[string]$Version,
|
||||
[ValidateSet("INSTALLER", "PORTABLE", "BOTH")]
|
||||
[string]$WindowsFormat = "INSTALLER"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -9,9 +11,10 @@ $ErrorActionPreference = "Stop"
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
Set-Location $repoRoot
|
||||
|
||||
$script:Version = ""
|
||||
$script:ResolvedVersion = ""
|
||||
$script:LinuxArch = ""
|
||||
$script:WindowsDone = $false
|
||||
$script:WindowsInstallerDone = $false
|
||||
$script:WindowsPortableDone = $false
|
||||
$script:LinuxDone = $false
|
||||
|
||||
function Write-Section {
|
||||
@@ -88,9 +91,9 @@ function Resolve-Version {
|
||||
|
||||
$explicit = Get-TrimmedText $ExplicitVersion
|
||||
if ($explicit -ne "") {
|
||||
$script:Version = Assert-VersionValue -Value $explicit -Source "传入版本号"
|
||||
$script:ResolvedVersion = Assert-VersionValue -Value $explicit -Source "传入版本号"
|
||||
Write-Host "[1/3] 使用传入版本号..."
|
||||
Write-Host "✓ 版本号: $script:Version"
|
||||
Write-Host "✓ 版本号: $script:ResolvedVersion"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
@@ -107,8 +110,8 @@ function Resolve-Version {
|
||||
throw "无法从 wails.json 读取版本号"
|
||||
}
|
||||
|
||||
$script:Version = Assert-VersionValue -Value $resolvedVersion -Source "wails.json productVersion"
|
||||
Write-Host "✓ 版本号: $script:Version"
|
||||
$script:ResolvedVersion = Assert-VersionValue -Value $resolvedVersion -Source "wails.json productVersion"
|
||||
Write-Host "✓ 版本号: $script:ResolvedVersion"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
@@ -126,15 +129,15 @@ function Invoke-WithTemporaryWailsVersion {
|
||||
|
||||
$currentConfig = Get-Content -LiteralPath $wailsConfigPath -Raw | ConvertFrom-Json
|
||||
$currentVersion = Get-TrimmedText ([string]$currentConfig.info.productVersion)
|
||||
if ($currentVersion -eq $script:Version) {
|
||||
if ($currentVersion -eq $script:ResolvedVersion) {
|
||||
& $ScriptBlock
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host " 临时覆盖 wails.json productVersion: $currentVersion -> $script:Version"
|
||||
Write-Host " 临时覆盖 wails.json productVersion: $currentVersion -> $script:ResolvedVersion"
|
||||
$originalBytes = [System.IO.File]::ReadAllBytes($wailsConfigPath)
|
||||
try {
|
||||
$currentConfig.info.productVersion = $script:Version
|
||||
$currentConfig.info.productVersion = $script:ResolvedVersion
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
$jsonText = ($currentConfig | ConvertTo-Json -Depth 100)
|
||||
[System.IO.File]::WriteAllText($wailsConfigPath, $jsonText + "`n", $utf8NoBom)
|
||||
@@ -190,6 +193,19 @@ function Resolve-PublishTarget {
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-WindowsFormat {
|
||||
param([string]$InputFormat)
|
||||
|
||||
$normalized = (Get-TrimmedText $InputFormat).ToUpperInvariant()
|
||||
if ($normalized -eq "") {
|
||||
return "INSTALLER"
|
||||
}
|
||||
if ($normalized -notin @("INSTALLER", "PORTABLE", "BOTH")) {
|
||||
throw "无效的 Windows 输出格式: $InputFormat`n 支持参数: INSTALLER/PORTABLE/BOTH"
|
||||
}
|
||||
return $normalized
|
||||
}
|
||||
|
||||
function Resolve-NsisPath {
|
||||
Write-Host "[Windows] 检测 NSIS 安装..."
|
||||
Write-Host " 支持环境变量:MAKENSIS_PATH / NSIS_PATH / NSIS_HOME"
|
||||
@@ -505,7 +521,7 @@ function Invoke-WindowsPackaging {
|
||||
Write-Host " NSIS 全局配置: disabled (/NOCONFIG)"
|
||||
}
|
||||
$nsisArguments = @(
|
||||
"/DVERSION=$script:Version",
|
||||
"/DVERSION=$script:ResolvedVersion",
|
||||
"/DSTAGINGDIR=$stagingAbs",
|
||||
"publish\installer.nsi"
|
||||
)
|
||||
@@ -521,6 +537,83 @@ function Invoke-WindowsPackaging {
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function New-WindowsPortableArchive {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$StagingDir
|
||||
)
|
||||
|
||||
Write-Host "[Windows] 生成便携 ZIP..."
|
||||
$outputDir = Join-Path $repoRoot "publish/output"
|
||||
if (-not (Test-Path -LiteralPath $outputDir)) {
|
||||
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$archiveName = "AntBrowser-$script:ResolvedVersion-windows-amd64-portable.zip"
|
||||
$archivePath = Join-Path $outputDir $archiveName
|
||||
$rootName = "AntBrowser-$script:ResolvedVersion-windows-amd64-portable"
|
||||
if (Test-Path -LiteralPath $archivePath) {
|
||||
Remove-Item -LiteralPath $archivePath -Force
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
$archiveStream = [System.IO.File]::Open(
|
||||
$archivePath,
|
||||
[System.IO.FileMode]::CreateNew,
|
||||
[System.IO.FileAccess]::Write,
|
||||
[System.IO.FileShare]::None
|
||||
)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive(
|
||||
$archiveStream,
|
||||
[System.IO.Compression.ZipArchiveMode]::Create,
|
||||
$false
|
||||
)
|
||||
try {
|
||||
$archive.CreateEntry("$rootName/") | Out-Null
|
||||
|
||||
foreach ($directory in (Get-ChildItem -LiteralPath $StagingDir -Directory -Recurse -Force)) {
|
||||
$relativePath = $directory.FullName.Substring($StagingDir.Length).TrimStart('\', '/')
|
||||
$entryName = "$rootName/$($relativePath.Replace('\', '/'))/"
|
||||
$archive.CreateEntry($entryName) | Out-Null
|
||||
}
|
||||
|
||||
foreach ($file in (Get-ChildItem -LiteralPath $StagingDir -File -Recurse -Force)) {
|
||||
$relativePath = $file.FullName.Substring($StagingDir.Length).TrimStart('\', '/')
|
||||
$entryName = "$rootName/$($relativePath.Replace('\', '/'))"
|
||||
$entry = $archive.CreateEntry(
|
||||
$entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal
|
||||
)
|
||||
$inputStream = [System.IO.File]::OpenRead($file.FullName)
|
||||
$outputStream = $entry.Open()
|
||||
try {
|
||||
$inputStream.CopyTo($outputStream)
|
||||
}
|
||||
finally {
|
||||
$outputStream.Dispose()
|
||||
$inputStream.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$archive.Dispose()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$archiveStream.Dispose()
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $archivePath -PathType Leaf)) {
|
||||
throw "便携 ZIP 生成失败: $archivePath"
|
||||
}
|
||||
|
||||
$script:WindowsPortableDone = $true
|
||||
Write-Host "✓ Windows 便携包生成成功: publish\output\$archiveName"
|
||||
Write-Host ""
|
||||
return $archivePath
|
||||
}
|
||||
|
||||
function Remove-WindowsStaging {
|
||||
param([string]$StagingDir)
|
||||
|
||||
@@ -533,23 +626,37 @@ function Remove-WindowsStaging {
|
||||
}
|
||||
|
||||
function Publish-Windows {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Format
|
||||
)
|
||||
|
||||
Write-Host "[3/3] 开始 Windows 打包..."
|
||||
Write-Host " 输出格式: $Format"
|
||||
Write-Host ""
|
||||
|
||||
$makensisPath = Resolve-NsisPath
|
||||
$makensisPath = $null
|
||||
if ($Format -in @("INSTALLER", "BOTH")) {
|
||||
$makensisPath = Resolve-NsisPath
|
||||
}
|
||||
Assert-RuntimeHashes -Target "windows-amd64"
|
||||
Build-WindowsBinary
|
||||
|
||||
$stagingDir = $null
|
||||
try {
|
||||
$stagingDir = New-WindowsStaging
|
||||
Invoke-WindowsPackaging -MakensisPath $makensisPath -StagingDir $stagingDir
|
||||
if ($Format -in @("INSTALLER", "BOTH")) {
|
||||
Invoke-WindowsPackaging -MakensisPath $makensisPath -StagingDir $stagingDir
|
||||
$script:WindowsInstallerDone = $true
|
||||
}
|
||||
if ($Format -in @("PORTABLE", "BOTH")) {
|
||||
New-WindowsPortableArchive -StagingDir $stagingDir | Out-Null
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-WindowsStaging -StagingDir $stagingDir
|
||||
}
|
||||
|
||||
$script:WindowsDone = $true
|
||||
Write-Host "✓ Windows 打包完成"
|
||||
Write-Host ""
|
||||
}
|
||||
@@ -565,7 +672,7 @@ function Publish-Linux {
|
||||
}
|
||||
|
||||
try {
|
||||
& powershell -NoProfile -ExecutionPolicy Bypass -File $linuxScript -RepoRoot $repoRoot -ArchOutFile $archOutFile -Version $script:Version
|
||||
& powershell -NoProfile -ExecutionPolicy Bypass -File $linuxScript -RepoRoot $repoRoot -ArchOutFile $archOutFile -Version $script:ResolvedVersion
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Linux 打包失败"
|
||||
}
|
||||
@@ -591,17 +698,18 @@ try {
|
||||
|
||||
Resolve-Version -ExplicitVersion $Version
|
||||
$publishTarget = Resolve-PublishTarget -InputTarget $Target
|
||||
$resolvedWindowsFormat = Resolve-WindowsFormat -InputFormat $WindowsFormat
|
||||
|
||||
Invoke-WithTemporaryWailsVersion {
|
||||
switch ($publishTarget) {
|
||||
"WINDOWS" {
|
||||
Publish-Windows
|
||||
Publish-Windows -Format $resolvedWindowsFormat
|
||||
}
|
||||
"LINUX" {
|
||||
Publish-Linux
|
||||
}
|
||||
"BOTH" {
|
||||
Publish-Windows
|
||||
Publish-Windows -Format $resolvedWindowsFormat
|
||||
Publish-Linux
|
||||
}
|
||||
default {
|
||||
@@ -613,8 +721,11 @@ try {
|
||||
Write-Host ""
|
||||
Write-Section "✓ 发布完成!"
|
||||
Write-Host ""
|
||||
if ($script:WindowsDone) {
|
||||
Write-Host "Windows 安装包: publish\output\AntBrowser-Setup-$script:Version.exe"
|
||||
if ($script:WindowsInstallerDone) {
|
||||
Write-Host "Windows 安装包: publish\output\AntBrowser-Setup-$script:ResolvedVersion.exe"
|
||||
}
|
||||
if ($script:WindowsPortableDone) {
|
||||
Write-Host "Windows 便携包: publish\output\AntBrowser-$script:ResolvedVersion-windows-amd64-portable.zip"
|
||||
}
|
||||
if ($script:LinuxDone) {
|
||||
Write-Host "Linux 产物目录: publish\output\"
|
||||
|
||||
Reference in New Issue
Block a user