chore: 官网拆分 homepage + 关于页瘦身 + 端口 5175(当前可用状态基线)
This commit is contained in:
1 parent
5eb11826db
commit
be151cb68f
85 files changed
+4420
-1530
No files matched your search
@@ -19,6 +19,11 @@ test('build.sh is valid Bash and documents supported native targets', () => {
|
||||
assert.match(help.stdout, /win-x64/)
|
||||
assert.match(help.stdout, /linux-x64/)
|
||||
assert.match(help.stdout, /不会创建或推送 Git tag/)
|
||||
assert.match(help.stdout, /src-tauri\/target\/<triple>\/release\/bundle\//)
|
||||
|
||||
const script = readFileSync(resolve(root, 'scripts/build.sh'), 'utf8')
|
||||
assert.match(script, /BUNDLE_ROOT=.*release\/bundle/)
|
||||
assert.doesNotMatch(script, /ROOT_DIR\/release\/v/)
|
||||
})
|
||||
|
||||
test('build.bat provides an equivalent native Windows x64 release path', () => {
|
||||
@@ -30,6 +35,8 @@ test('build.bat provides an equivalent native Windows x64 release path', () => {
|
||||
assert.match(script, /nsis\\\*\.exe/)
|
||||
assert.match(script, /SHA256SUMS\.txt/)
|
||||
assert.match(script, /不会创建或推送 Git tag/)
|
||||
assert.match(script, /src-tauri\\target\\<triple>\\release\\bundle\\/)
|
||||
assert.doesNotMatch(script, /release\\v%VERSION%/)
|
||||
})
|
||||
|
||||
test('release metadata versions stay synchronized', () => {
|
||||
|
||||
+9
-22
@@ -132,24 +132,9 @@ set "BUNDLE_ROOT=src-tauri\target\%RUST_TARGET%\release\bundle"
|
||||
node scripts\verify-release.mjs windows "%BUNDLE_ROOT%"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
set "OUTPUT_DIR=%CD%\release\v%VERSION%\%OUTPUT_PLATFORM%"
|
||||
if exist "%OUTPUT_DIR%" rmdir /S /Q "%OUTPUT_DIR%"
|
||||
mkdir "%OUTPUT_DIR%"
|
||||
if errorlevel 1 (
|
||||
call :fail "无法创建输出目录:%OUTPUT_DIR%"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set /A ARTIFACT_COUNT=0
|
||||
for %%F in ("%BUNDLE_ROOT%\nsis\*.exe" "%BUNDLE_ROOT%\msi\*.msi" "%BUNDLE_ROOT%\wix\*.msi") do (
|
||||
if exist "%%~fF" (
|
||||
copy /Y "%%~fF" "%OUTPUT_DIR%\%%~nxF" >nul
|
||||
if errorlevel 1 (
|
||||
call :fail "复制安装包失败:%%~fF"
|
||||
exit /b 1
|
||||
)
|
||||
set /A ARTIFACT_COUNT+=1
|
||||
)
|
||||
if exist "%%~fF" set /A ARTIFACT_COUNT+=1
|
||||
)
|
||||
|
||||
if "%ARTIFACT_COUNT%"=="0" (
|
||||
@@ -158,19 +143,21 @@ if "%ARTIFACT_COUNT%"=="0" (
|
||||
)
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"$files = Get-ChildItem -LiteralPath $env:OUTPUT_DIR -File ^| Where-Object { $_.Extension -in '.exe', '.msi' };" ^
|
||||
"$root = $env:BUNDLE_ROOT;" ^
|
||||
"$files = @(); foreach ($dir in @('nsis','msi','wix')) { $p = Join-Path $root $dir; if (Test-Path $p) { $files += Get-ChildItem -LiteralPath $p -File ^| Where-Object { $_.Extension -in '.exe', '.msi' } } };" ^
|
||||
"if ($files.Count -eq 0) { throw 'no installers' };" ^
|
||||
"$lines = @($files ^| ForEach-Object { (Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName).Hash.ToLowerInvariant() + ' ' + $_.Name });" ^
|
||||
"[System.IO.File]::WriteAllLines((Join-Path $env:OUTPUT_DIR 'SHA256SUMS.txt'), [string[]]$lines, (New-Object System.Text.UTF8Encoding($false)))"
|
||||
"[System.IO.File]::WriteAllLines((Join-Path $root 'SHA256SUMS.txt'), [string[]]$lines, (New-Object System.Text.UTF8Encoding($false)))"
|
||||
if errorlevel 1 (
|
||||
call :fail "生成 SHA256SUMS.txt 失败"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
call :log "构建完成:%OUTPUT_DIR%"
|
||||
dir "%OUTPUT_DIR%"
|
||||
call :log "构建完成(Tauri bundle):%BUNDLE_ROOT%"
|
||||
dir /S /B "%BUNDLE_ROOT%\nsis\*.exe" "%BUNDLE_ROOT%\msi\*.msi" "%BUNDLE_ROOT%\wix\*.msi" 2>nul
|
||||
echo.
|
||||
echo [build] SHA-256
|
||||
type "%OUTPUT_DIR%\SHA256SUMS.txt"
|
||||
type "%BUNDLE_ROOT%\SHA256SUMS.txt"
|
||||
exit /b 0
|
||||
|
||||
:usage_ok
|
||||
@@ -199,7 +186,7 @@ echo Visual Studio 2022 Build Tools(Desktop development with C++)、
|
||||
echo Windows 10/11 SDK、WebView2 Runtime。
|
||||
echo.
|
||||
echo 说明:
|
||||
echo - 输出 NSIS/MSI 到 release\v版本\windows-x64 并生成 SHA256SUMS.txt。
|
||||
echo - 安装包留在 src-tauri\target\<triple>\release\bundle\(nsis/msi),并生成 SHA256SUMS.txt。
|
||||
echo - 脚本不会创建或推送 Git tag,也不会上传 Release。
|
||||
echo - 正式签名需设置 REQUIRE_SIGNING=true 和 Windows 签名环境变量。
|
||||
exit /b 0
|
||||
|
||||
+24
-36
@@ -42,6 +42,7 @@ OpsXShell 原生安装包构建脚本
|
||||
- 显式传入新版本时,会同步 package.json、package-lock.json、
|
||||
Cargo.toml、Cargo.lock 和 tauri.conf.json,但不会创建或推送 Git tag。
|
||||
- Windows 包必须在 Windows 上构建;macOS 包必须在 macOS 上构建;Linux 包必须在 Linux 上构建。
|
||||
- 安装包留在 Tauri 标准目录 src-tauri/target/<triple>/release/bundle/,不拷到仓库根 release/。
|
||||
- 正式签名构建请先设置 REQUIRE_SIGNING=true 及对应签名环境变量。
|
||||
EOF
|
||||
}
|
||||
@@ -198,12 +199,8 @@ fi
|
||||
BUNDLE_ROOT="${CARGO_TARGET_DIR:-$ROOT_DIR/src-tauri/target}/$RUST_TARGET/release/bundle"
|
||||
node scripts/verify-release.mjs "$RELEASE_PLATFORM" "$BUNDLE_ROOT"
|
||||
|
||||
OUTPUT_DIR="$ROOT_DIR/release/v$VERSION/$OUTPUT_PLATFORM"
|
||||
rm -rf "$OUTPUT_DIR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
# 产物留在 Tauri 标准目录,不再拷到仓库根 release/
|
||||
shopt -s nullglob
|
||||
|
||||
COPIED_ARTIFACTS=()
|
||||
if [ "$RELEASE_PLATFORM" = "macos" ]; then
|
||||
SOURCE_ARTIFACTS=("$BUNDLE_ROOT"/dmg/*.dmg)
|
||||
elif [ "$RELEASE_PLATFORM" = "linux" ]; then
|
||||
@@ -213,39 +210,30 @@ else
|
||||
fi
|
||||
|
||||
[ "${#SOURCE_ARTIFACTS[@]}" -gt 0 ] || fail "构建完成但未找到安装包:$BUNDLE_ROOT"
|
||||
|
||||
checksum_line() {
|
||||
local artifact="$1" digest
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
digest="$(shasum -a 256 "$artifact" | awk '{print $1}')"
|
||||
elif command -v sha256sum >/dev/null 2>&1; then
|
||||
digest="$(sha256sum "$artifact" | awk '{print $1}')"
|
||||
else
|
||||
digest="$(node --input-type=module -e '
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
console.log(createHash("sha256").update(readFileSync(process.argv[1])).digest("hex"));
|
||||
' "$artifact")"
|
||||
fi
|
||||
printf '%s %s\n' "$digest" "$(basename "$artifact")"
|
||||
}
|
||||
|
||||
CHECKSUM_FILE="$BUNDLE_ROOT/SHA256SUMS.txt"
|
||||
: >"$CHECKSUM_FILE"
|
||||
for artifact in "${SOURCE_ARTIFACTS[@]}"; do
|
||||
destination="$OUTPUT_DIR/$(basename "$artifact")"
|
||||
cp "$artifact" "$destination"
|
||||
COPIED_ARTIFACTS+=("$destination")
|
||||
checksum_line "$artifact" >>"$CHECKSUM_FILE"
|
||||
done
|
||||
|
||||
CHECKSUM_FILE="$OUTPUT_DIR/SHA256SUMS.txt"
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
(
|
||||
cd "$OUTPUT_DIR"
|
||||
names=()
|
||||
for artifact in "${COPIED_ARTIFACTS[@]}"; do names+=("$(basename "$artifact")"); done
|
||||
shasum -a 256 "${names[@]}" > SHA256SUMS.txt
|
||||
)
|
||||
elif command -v sha256sum >/dev/null 2>&1; then
|
||||
(
|
||||
cd "$OUTPUT_DIR"
|
||||
names=()
|
||||
for artifact in "${COPIED_ARTIFACTS[@]}"; do names+=("$(basename "$artifact")"); done
|
||||
sha256sum "${names[@]}" > SHA256SUMS.txt
|
||||
)
|
||||
else
|
||||
node --input-type=module -e '
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { basename } from "node:path";
|
||||
for (const path of process.argv.slice(1)) {
|
||||
console.log(`${createHash("sha256").update(readFileSync(path)).digest("hex")} ${basename(path)}`);
|
||||
}
|
||||
' "${COPIED_ARTIFACTS[@]}" > "$CHECKSUM_FILE"
|
||||
fi
|
||||
|
||||
log "构建完成:$OUTPUT_DIR"
|
||||
ls -lh "$OUTPUT_DIR"
|
||||
log "构建完成(Tauri bundle):$BUNDLE_ROOT ($OUTPUT_PLATFORM)"
|
||||
ls -lh "${SOURCE_ARTIFACTS[@]}"
|
||||
printf '\n[build] SHA-256\n'
|
||||
cat "$CHECKSUM_FILE"
|
||||
@@ -0,0 +1,109 @@
|
||||
# sourced by scripts/run.sh — temp OpsxShell.app for Dock icon (icons/icon.icns)
|
||||
# shellcheck shell=bash
|
||||
|
||||
# .noindex:避免 Spotlight 把临时 Dock 壳当成可打开的历史 App
|
||||
GUI_APP_DIR="$SCRIPT_DIR/src-tauri/target/gui-app.noindex"
|
||||
GUI_APP="$GUI_APP_DIR/OpsxShell.app"
|
||||
ICONS_DIR="$SCRIPT_DIR/src-tauri/icons"
|
||||
REL_APP="$SCRIPT_DIR/src-tauri/target/release/bundle/macos/OpsxShell.app"
|
||||
[ -d "$REL_APP" ] || REL_APP="$SCRIPT_DIR/src-tauri/target/release/bundle/macos/OpsXShell.app"
|
||||
DBG_APP="$SCRIPT_DIR/src-tauri/target/debug/bundle/macos/OpsxShell.app"
|
||||
[ -d "$DBG_APP" ] || DBG_APP="$SCRIPT_DIR/src-tauri/target/debug/bundle/macos/OpsXShell.app"
|
||||
REL_BIN="$SCRIPT_DIR/src-tauri/target/release/OpsxShell"
|
||||
[ -x "$REL_BIN" ] || REL_BIN="$SCRIPT_DIR/src-tauri/target/release/OpsXShell"
|
||||
DBG_BIN_A="$SCRIPT_DIR/src-tauri/target/debug/OpsxShell"
|
||||
[ -x "$DBG_BIN_A" ] || DBG_BIN_A="$SCRIPT_DIR/src-tauri/target/debug/OpsXShell"
|
||||
DBG_BIN_B="$SCRIPT_DIR/src-tauri/target/debug/opsxshell"
|
||||
GUI_DEV_NEEDS_VITE=0
|
||||
|
||||
_gui_write_plist() {
|
||||
cat >"$GUI_APP/Contents/Info.plist" <<'PLIST'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key><string>OpsxShell</string>
|
||||
<key>CFBundleExecutable</key><string>OpsxShell</string>
|
||||
<key>CFBundleIconFile</key><string>icon.icns</string>
|
||||
<key>CFBundleIdentifier</key><string>com.opsxshell.desktop.dev</string>
|
||||
<key>CFBundleName</key><string>OpsxShell</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>2.1.1-dev</string>
|
||||
<key>CFBundleVersion</key><string>0</string>
|
||||
<key>LSHighResolutionCapable</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
}
|
||||
|
||||
_gui_wrap_bin() {
|
||||
local src="$1"
|
||||
mkdir -p "$GUI_APP/Contents/MacOS" "$GUI_APP/Contents/Resources"
|
||||
cp "$src" "$GUI_APP/Contents/MacOS/OpsxShell"
|
||||
chmod +x "$GUI_APP/Contents/MacOS/OpsxShell"
|
||||
_gui_write_plist
|
||||
}
|
||||
|
||||
ensure_gui_dev_app() {
|
||||
local icon="$ICONS_DIR/icon.icns"
|
||||
GUI_DEV_NEEDS_VITE=0
|
||||
if [ ! -f "$icon" ]; then
|
||||
error "缺少 ${icon}"
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "$GUI_APP_DIR"
|
||||
if [ -d "$DBG_APP" ]; then
|
||||
rm -rf "$GUI_APP"
|
||||
cp -R "$DBG_APP" "$GUI_APP"
|
||||
GUI_DEV_NEEDS_VITE=1
|
||||
elif [ -x "$DBG_BIN_A" ]; then
|
||||
rm -rf "$GUI_APP"
|
||||
_gui_wrap_bin "$DBG_BIN_A"
|
||||
GUI_DEV_NEEDS_VITE=1
|
||||
elif [ -x "$DBG_BIN_B" ]; then
|
||||
rm -rf "$GUI_APP"
|
||||
_gui_wrap_bin "$DBG_BIN_B"
|
||||
GUI_DEV_NEEDS_VITE=1
|
||||
elif [ -d "$REL_APP" ]; then
|
||||
rm -rf "$GUI_APP"
|
||||
cp -R "$REL_APP" "$GUI_APP"
|
||||
elif [ -x "$REL_BIN" ]; then
|
||||
rm -rf "$GUI_APP"
|
||||
_gui_wrap_bin "$REL_BIN"
|
||||
else
|
||||
error "没有可包装的桌面二进制。请先 ./run.sh gui 走一次 tauri 编译,或 npm run tauri -- build --no-bundle。"
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "$GUI_APP/Contents/Resources"
|
||||
cp "$icon" "$GUI_APP/Contents/Resources/icon.icns"
|
||||
# 拷来的正式包可能仍叫 OpsXShell:统一成 OpsxShell(与 OpsxAPI / OpsxDB 同一套大小写)
|
||||
if [ -x "$GUI_APP/Contents/MacOS/OpsXShell" ] && [ ! -x "$GUI_APP/Contents/MacOS/OpsxShell" ]; then
|
||||
mv "$GUI_APP/Contents/MacOS/OpsXShell" "$GUI_APP/Contents/MacOS/OpsxShell"
|
||||
fi
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName OpsxShell" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleName OpsxShell" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable OpsxShell" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleIconFile icon.icns" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.opsxshell.desktop.dev" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
local stamp
|
||||
stamp="$(date +%Y%m%d%H%M%S)"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${stamp}" "$GUI_APP/Contents/Info.plist" 2>/dev/null || true
|
||||
touch "$GUI_APP"
|
||||
success "临时 .app 已刷 icons/icon.icns → ${GUI_APP}"
|
||||
}
|
||||
|
||||
kill_opsxshell_gui() {
|
||||
stop_detached "$DESKTOP_LABEL"
|
||||
pkill -f "$SCRIPT_DIR/src-tauri/target/debug/opsxshell" 2>/dev/null || true
|
||||
pkill -f "$SCRIPT_DIR/src-tauri/target/debug/OpsXShell" 2>/dev/null || true
|
||||
pkill -f "$SCRIPT_DIR/src-tauri/target/debug/OpsxShell" 2>/dev/null || true
|
||||
pkill -f "$SCRIPT_DIR/node_modules/.bin/tauri" 2>/dev/null || true
|
||||
pkill -f "$GUI_APP" 2>/dev/null || true
|
||||
pkill -x OpsxShell 2>/dev/null || true
|
||||
pkill -x OpsXShell 2>/dev/null || true
|
||||
}
|
||||
|
||||
gui_is_running() {
|
||||
pgrep -f "$GUI_APP/Contents/MacOS/" >/dev/null 2>&1 \
|
||||
|| pgrep -x OpsxShell >/dev/null 2>&1
|
||||
}
|
||||
@@ -6,7 +6,30 @@ set -u
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERSION="$(node -p "require('$ROOT_DIR/package.json').version")"
|
||||
DMG="${1:-$ROOT_DIR/release/v$VERSION/macos-arm64/OpsXShell_${VERSION}_aarch64.dmg}"
|
||||
|
||||
default_dmg() {
|
||||
local dir dmg
|
||||
for dir in \
|
||||
"$ROOT_DIR/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg" \
|
||||
"$ROOT_DIR/src-tauri/target/x86_64-apple-darwin/release/bundle/dmg" \
|
||||
"$ROOT_DIR/src-tauri/target/release/bundle/dmg"
|
||||
do
|
||||
dmg="$(ls -t "$dir"/OpsXShell_"${VERSION}"_*.dmg 2>/dev/null | head -n 1 || true)"
|
||||
if [ -n "$dmg" ]; then
|
||||
printf '%s' "$dmg"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ -n "${1:-}" ]; then
|
||||
DMG="$1"
|
||||
elif ! DMG="$(default_dmg)"; then
|
||||
echo "找不到 Tauri bundle 里的 DMG。请先 ./scripts/build.sh,或显式传入路径。" >&2
|
||||
echo "标准位置:src-tauri/target/<triple>/release/bundle/dmg/" >&2
|
||||
exit 1
|
||||
fi
|
||||
APPLE_ID="${APPLE_ID:-}"
|
||||
TEAM_ID="${APPLE_TEAM_ID:-B8DVUUUF6W}"
|
||||
WAIT_TIMEOUT="${NOTARY_WAIT_TIMEOUT:-15m}"
|
||||
|
||||
+35
-14
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create an OpsXShell Gitee Release and upload native installers.
|
||||
|
||||
The script is intentionally non-destructive: release artifacts stay in the
|
||||
local ``release/`` directory after a successful upload.
|
||||
The script is intentionally non-destructive: artifacts stay in Tauri's
|
||||
``src-tauri/target/<triple>/release/bundle/`` after a successful upload.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -74,23 +74,44 @@ def load_token() -> str:
|
||||
fail("缺少 Gitee Token:请设置 GITEE_TOKEN 或 ~/.netrc 的 gitee.com password")
|
||||
|
||||
|
||||
def collect_assets(root: Path, version: str) -> list[Path]:
|
||||
version_dir = root / "release" / version
|
||||
if not version_dir.is_dir():
|
||||
fail(f"发布目录不存在:{version_dir}")
|
||||
TAURI_BUNDLE_ROOTS = (
|
||||
"src-tauri/target/release/bundle",
|
||||
"src-tauri/target/aarch64-apple-darwin/release/bundle",
|
||||
"src-tauri/target/x86_64-apple-darwin/release/bundle",
|
||||
"src-tauri/target/x86_64-pc-windows-msvc/release/bundle",
|
||||
"src-tauri/target/x86_64-unknown-linux-gnu/release/bundle",
|
||||
"src-tauri/target/aarch64-unknown-linux-gnu/release/bundle",
|
||||
)
|
||||
|
||||
assets = sorted(
|
||||
path
|
||||
for path in version_dir.rglob("*")
|
||||
if path.is_file()
|
||||
and (path.suffix.lower() in ALLOWED_SUFFIXES or path.name in CHECKSUM_NAMES)
|
||||
)
|
||||
|
||||
def collect_assets(root: Path, version: str) -> list[Path]:
|
||||
version_num = version.removeprefix("v")
|
||||
found: list[Path] = []
|
||||
for relative in TAURI_BUNDLE_ROOTS:
|
||||
bundle = root / relative
|
||||
if not bundle.is_dir():
|
||||
continue
|
||||
for path in bundle.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if path.name.startswith("rw."):
|
||||
continue
|
||||
if path.name in CHECKSUM_NAMES:
|
||||
found.append(path)
|
||||
continue
|
||||
if path.suffix.lower() in ALLOWED_SUFFIXES and version_num in path.name:
|
||||
found.append(path)
|
||||
|
||||
assets = sorted(set(found))
|
||||
installers = [path for path in assets if path.suffix.lower() in ALLOWED_SUFFIXES]
|
||||
checksums = [path for path in assets if path.name in CHECKSUM_NAMES]
|
||||
if not installers:
|
||||
fail(f"{version_dir} 中没有安装包")
|
||||
fail(
|
||||
"未在 src-tauri/target/<triple>/release/bundle/ 找到安装包;"
|
||||
f"请先构建 v{version_num}(不要使用仓库根 release/)"
|
||||
)
|
||||
if not checksums:
|
||||
fail(f"{version_dir} 中没有 SHA256SUMS.txt")
|
||||
fail("bundle 目录中没有 SHA256SUMS.txt(由 ./scripts/build.sh 生成)")
|
||||
|
||||
duplicate_names = {path.name for path in assets if sum(item.name == path.name for item in assets) > 1}
|
||||
if duplicate_names:
|
||||
|
||||
+61
-38
@@ -1,22 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# OpsXShell launcher — scene profiles(对齐 opsxdb / opsxapi)
|
||||
# OpsxShell launcher — scene profiles(对齐 opsxdb / opsxapi)
|
||||
#
|
||||
# ./run.sh # = web:仅 Gin 官网 + docs(:8789)
|
||||
# ./run.sh web # 同上(浏览器不当操作客户端)
|
||||
# ./run.sh gui # 桌面唯一操作入口:Tauri;默认也起 Gin
|
||||
# ./run.sh gui # 桌面唯一操作入口:临时 .app(Dock 图标)+ 默认 Gin
|
||||
# ./run.sh all # 同 gui(兼容旧命令)
|
||||
# ./run.sh server|desktop
|
||||
# ./run.sh clean-apps # 全量开发机卫生(~/devops/scripts/clean-opsx-apps.sh)
|
||||
# ./run.sh stop|restart|status|help
|
||||
#
|
||||
# 产品形态:浏览器 = 官网/docs;操作 = 桌面 Tauri(src/ + src-tauri/)。
|
||||
# Vite :1420 仅作桌面 WebView 源,不要用浏览器当工作台。
|
||||
# Vite :5175 仅作桌面 WebView 源,不要用浏览器当工作台。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_NAME="OpsXShell"
|
||||
PROJECT_NAME="OpsxShell"
|
||||
SERVER_PORT="${OPSXSHELL_SERVER_PORT:-8789}"
|
||||
WEB_PORT="${OPSXSHELL_WEB_PORT:-1420}"
|
||||
SERVER_DIR="apps/server"
|
||||
WEB_PORT="${OPSXSHELL_WEB_PORT:-5175}"
|
||||
SERVER_DIR="server"
|
||||
SERVER_BIN="opsxshell-server"
|
||||
SERVER_LABEL="com.opsx.shell.server"
|
||||
WEB_LABEL="com.opsx.shell.web"
|
||||
@@ -39,6 +40,36 @@ SERVER_LOG="$LOG_DIR/server.log"
|
||||
WEB_LOG="$LOG_DIR/web.log"
|
||||
DESKTOP_LOG="$LOG_DIR/desktop.log"
|
||||
|
||||
# shellcheck source=gui-dev-app.sh
|
||||
source "$SCRIPT_DIR/scripts/gui-dev-app.sh"
|
||||
|
||||
OPSX_CLEAN_SCRIPT="${OPSX_CLEAN_SCRIPT:-$HOME/devops/scripts/clean-opsx-apps.sh}"
|
||||
|
||||
# gui 启动前轻量清:推残留 DMG + 注销非 /Applications、非 keep-path 的幽灵。
|
||||
# 不删 target、不 ResetLaunchPad / kill Dock。逻辑在 clean-opsx-apps.sh --light。
|
||||
opsx_light_clean_before_gui() {
|
||||
local keep="$1"
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ ! -x "$OPSX_CLEAN_SCRIPT" ]; then
|
||||
warning "找不到清理脚本: $OPSX_CLEAN_SCRIPT(跳过轻量清)"
|
||||
return 0
|
||||
fi
|
||||
info "gui light-clean (keep-path=${keep})..."
|
||||
"$OPSX_CLEAN_SCRIPT" --light --keep-path="$keep" --no-verify \
|
||||
|| warning "轻量清未完全成功(继续启动)"
|
||||
}
|
||||
|
||||
run_clean_apps() {
|
||||
if [ ! -x "$OPSX_CLEAN_SCRIPT" ]; then
|
||||
error "找不到: $OPSX_CLEAN_SCRIPT"
|
||||
return 1
|
||||
fi
|
||||
# 转发剩余参数(如 --delete-target-apps);默认 full
|
||||
"$OPSX_CLEAN_SCRIPT" "$@"
|
||||
}
|
||||
|
||||
# Same path as src-tauri infrastructure/file_log.rs (dirs::data_local_dir).
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
APP_LOG="$HOME/Library/Application Support/opsxshell/logs/opsxshell.log"
|
||||
@@ -174,14 +205,14 @@ start_vite_for_desktop() {
|
||||
success "桌面 UI(Vite) 已在运行 :$WEB_PORT"
|
||||
return 0
|
||||
fi
|
||||
info "启动桌面 UI 源 :$WEB_PORT(仅本机给 Tauri,勿当浏览器客户端)..."
|
||||
info "启动桌面 UI 源 :${WEB_PORT}(仅本机给 Tauri,勿当浏览器客户端)..."
|
||||
: >"$WEB_LOG"
|
||||
ensure_npm
|
||||
kill_port "$WEB_PORT"
|
||||
start_detached "$WEB_LABEL" "$WEB_LOG" \
|
||||
"export PATH=\"$NODE_BIN_DIR:/opt/homebrew/bin:/usr/local/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run dev:ui"
|
||||
wait_http "http://127.0.0.1:$WEB_PORT/" "Vite" 80
|
||||
success "桌面 UI(Vite) UP http://127.0.0.1:$WEB_PORT(请用 ./run.sh gui)"
|
||||
success "桌面 UI(Vite) UP http://127.0.0.1:${WEB_PORT}(请用 ./run.sh gui)"
|
||||
}
|
||||
|
||||
stop_web() {
|
||||
@@ -191,32 +222,26 @@ stop_web() {
|
||||
}
|
||||
|
||||
start_desktop() {
|
||||
info "启动桌面 OpsXShell ..."
|
||||
opsx_light_clean_before_gui "$GUI_APP"
|
||||
info "启动桌面 OpsxShell(临时 .app + 当前图标)..."
|
||||
: >"$DESKTOP_LOG"
|
||||
ensure_npm
|
||||
# 清理旧进程
|
||||
pkill -f 'target/debug/opsxshell' 2>/dev/null || true
|
||||
pkill -f 'target/debug/OpsXShell' 2>/dev/null || true
|
||||
stop_detached "$DESKTOP_LABEL"
|
||||
# tauri dev 会自己起 Vite;detached 跑 npm run dev
|
||||
start_detached "$DESKTOP_LABEL" "$DESKTOP_LOG" \
|
||||
"export PATH=\"$NODE_BIN_DIR:/opt/homebrew/bin:/usr/local/bin:\$HOME/.cargo/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run dev"
|
||||
kill_opsxshell_gui
|
||||
ensure_gui_dev_app || return 1
|
||||
if [ "${GUI_DEV_NEEDS_VITE:-0}" = "1" ]; then
|
||||
start_vite_for_desktop
|
||||
fi
|
||||
open "$GUI_APP" 2>>"$DESKTOP_LOG" || { error "open 失败,见 $DESKTOP_LOG"; return 1; }
|
||||
|
||||
local i=0
|
||||
while true; do
|
||||
if pgrep -f 'target/debug/opsxshell' >/dev/null 2>&1 \
|
||||
|| pgrep -f 'target/debug/OpsXShell' >/dev/null 2>&1; then
|
||||
success "桌面 OpsXShell 已启动"
|
||||
info "应用日志: tail -f \"$APP_LOG\""
|
||||
if gui_is_running; then
|
||||
success "桌面 OpsxShell 已启动(临时 .app,Dock 用 icon.icns / 名称 OpsxShell)"
|
||||
info "路径: $GUI_APP"
|
||||
return 0
|
||||
fi
|
||||
if grep -Eiq 'error:|panicked|could not compile' "$DESKTOP_LOG" 2>/dev/null; then
|
||||
error "桌面启动失败,见 $DESKTOP_LOG"
|
||||
tail -n 40 "$DESKTOP_LOG" || true
|
||||
return 1
|
||||
fi
|
||||
i=$((i + 1))
|
||||
if [ "$i" -ge 180 ]; then
|
||||
if [ "$i" -ge 30 ]; then
|
||||
error "桌面启动超时,见 $DESKTOP_LOG"
|
||||
tail -n 40 "$DESKTOP_LOG" || true
|
||||
return 1
|
||||
@@ -226,10 +251,7 @@ start_desktop() {
|
||||
}
|
||||
|
||||
stop_desktop() {
|
||||
stop_detached "$DESKTOP_LABEL"
|
||||
pkill -f 'target/debug/opsxshell' 2>/dev/null || true
|
||||
pkill -f 'target/debug/OpsXShell' 2>/dev/null || true
|
||||
pkill -f 'node_modules/.bin/tauri' 2>/dev/null || true
|
||||
kill_opsxshell_gui
|
||||
success "桌面已停止"
|
||||
}
|
||||
|
||||
@@ -241,7 +263,7 @@ status() {
|
||||
warning "server :$SERVER_PORT DOWN"
|
||||
fi
|
||||
if curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1; then
|
||||
success "vite :$WEB_PORT UP(桌面源,非浏览器产品)"
|
||||
success "vite :${WEB_PORT} UP(桌面源,非浏览器产品)"
|
||||
else
|
||||
warning "vite :$WEB_PORT DOWN"
|
||||
fi
|
||||
@@ -249,11 +271,10 @@ status() {
|
||||
|| curl -fsS "http://127.0.0.1:$SERVER_PORT/docs" >/dev/null 2>&1; then
|
||||
success "site :$SERVER_PORT 官网/docs 可达"
|
||||
fi
|
||||
if pgrep -f 'target/debug/opsxshell' >/dev/null 2>&1 \
|
||||
|| pgrep -f 'target/debug/OpsXShell' >/dev/null 2>&1; then
|
||||
success "gui OpsXShell UP"
|
||||
if gui_is_running; then
|
||||
success "gui OpsxShell UP"
|
||||
else
|
||||
warning "gui OpsXShell DOWN"
|
||||
warning "gui OpsxShell DOWN"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -263,14 +284,15 @@ $PROJECT_NAME 启动脚本(产品形态:浏览器=官网/docs · 操作=桌
|
||||
|
||||
./run.sh # 同 web
|
||||
./run.sh web # 仅 Gin:官网 + docs → http://127.0.0.1:$SERVER_PORT/
|
||||
./run.sh gui # 桌面唯一操作入口(Tauri;默认也起 Gin)
|
||||
./run.sh gui # 桌面:临时 .app(Dock=OpsxShell / icon.icns;默认也起 Gin)
|
||||
./run.sh all # 同 gui(兼容旧名)
|
||||
|
||||
./run.sh server # 只 Gin(官网/docs)
|
||||
./run.sh desktop # 只 Desktop
|
||||
./run.sh stop | restart | status | help
|
||||
./run.sh clean-apps # 全量开发机卫生(默认只注销不删 target)
|
||||
|
||||
端口: Gin/官网 $SERVER_PORT · 桌面 Vite 源 $WEB_PORT(不对外当客户端)
|
||||
端口: Gin/官网 $SERVER_PORT · 桌面 Vite 源 ${WEB_PORT}(不对外当客户端)
|
||||
日志: logs/{server,web,desktop}.log · 应用日志见 $APP_LOG
|
||||
USAGE
|
||||
}
|
||||
@@ -291,7 +313,7 @@ start_gui_scene() {
|
||||
fi
|
||||
start_desktop
|
||||
echo
|
||||
success "GUI 场景就绪 → 桌面窗口 OpsXShell"
|
||||
success "GUI 场景就绪 → 桌面窗口 OpsxShell"
|
||||
}
|
||||
|
||||
start_all_scene() {
|
||||
@@ -321,6 +343,7 @@ case "$cmd" in
|
||||
start_web_scene
|
||||
;;
|
||||
status) status ;;
|
||||
clean-apps) run_clean_apps "${@:2}" ;;
|
||||
help|-h|--help) usage ;;
|
||||
*) error "未知命令: $cmd"; usage; exit 1 ;;
|
||||
esac
|
||||
Reference in new issue
Block a user